如何在使用“ls”命令以及将输出分配给变量时抑制标准错误?

Posted

技术标签:

【中文标题】如何在使用“ls”命令以及将输出分配给变量时抑制标准错误?【英文标题】:How to suppress stderr while using 'ls' command along with assigning the output to a variable? 【发布时间】:2020-01-04 17:30:42 【问题描述】:

我正在尝试创建一个脚本,该脚本将根据名称和时间戳在服务器上搜索文件,并将这些文件计数到一个变量中。当文件可用时它可以正常工作,但当文件不可用时会引发 stderr。

为了抑制标准错误,我试图将其重定向到 /dev/null 但即使这样也无济于事,错误仍然显示在屏幕上。我知道我可以先使用“if”语句检查文件是否可用,然后进行计数,但这会不必要地使脚本冗长。

那么有没有一种方法可以获取文件计数并抑制 stderr(如果有),同时仅在一行代码中将输出分配给变量?

当服务器上存在名为“example”的文件时,此命令将成功运行:

file_name=example
file_date=20190901

file_count=`ls -lrt "$PWD"/"$file_name"*"$file_date"* | wc -l`

但是当文件不存在时,它会在屏幕上抛出一个标准错误,如下所示:

ls: cannot access /home/saurap01/example*20190901*: No such file or directory

为了抑制此错误,我尝试将其重定向到 /dev/null,如下所示:

file_count=`ls -lrt "$PWD"/"$file_name"*"$file_date"* | wc -l` > /dev/null 2>&1

但即使这样也无助于抑制错误。

我可以尝试使用以下方法隐藏标准错误:

file_count=`ls -lrt "$PWD"/"$file_name"*"$file_date"* | wc -l` 2>&-

【问题讨论】:

Why not parse ls? 为什么要使用-r-t 【参考方案1】:

您没有正确重定向 stderr,2>/dev/null 应该紧跟在您要抑制其 stderr 的命令之后,例如:

file_count=`ls -lrt "$PWD"/"$file_name"*"$file_date"* 2>/dev/null | wc -l`

但是, parsing the output of ls is not a good idea at all,你应该使用 find 来完成这样的任务。例如,使用 GNU 查找:

find -maxdepth 1 \
     -type f \
     -name "$file_name*$file_date*" \
     -printf '.' | wc -c

或使用任何符合 POSIX 的查找:

find . \
! \( -type d -path '*/*' -prune \) \
     -type f \
     -name "$file_name*$file_date*" \
     -exec printf '.%.s'  + | wc -c

【讨论】:

谢谢!使用重定向标准错误对我有用。【参考方案2】:

直接的解决办法是添加

2>/dev/null

命令替换。

您的代码展示了许多反模式,所以让我们也讨论一下。

正如useless use of ls 中所述,shell 在将参数传递给ls 之前已经执行了通配符扩展。此外,您传递给ls 的选项执行了大量的额外工作;您忽略了大小、所有者等,但您使用 -l 选项查找这些内容;而且您不关心排序顺序,但您使用-rt 强制按时间戳排序。

此外,如http://mywiki.wooledge.org/ParsingLs 中所述,ls 命令具有许多人类可读输出的功能,因此不适合在脚本中使用。

开箱即用的外壳通常配置有 glob 设置,如果它不匹配任何文件,则返回 glob 模式本身。在 Bash(但不是 ksh)中,您可以通过

来避免这种情况
setopt -s nullglob

然后简单地打印文件;

### BUG; see below
printf '%s\n' ./"$file_name"*"$file_date"* |
wc -l

但是,如果其中一个文件名包含换行符,则会产生错误的结果 - 然后,输出的行数将与文件数不同。一个简单的解决方法是避免使用 printf 并完全使用 shell 的功能。

set -- ./"$file_name"*"$file_date"*

Thrs 将简单地将通配符匹配分配给参数列表$1$2 等,因此

echo $#

将打印文件数。

如果您没有 Bash(因此没有 shopt),您可以通过检查第一个文件是否存在来查找 glob 是否扩展到自身。

set -- ./"$file_name"*"$file_date"*
if [ -e "$1" ]; then
    echo "$#"
else
    echo 0
fi

除此之外,还要注意"$PWD" 很难拼出。如果您需要将相对文件名转换为绝对文件名,或者出于其他原因需要知道当前目录的完整路径,它偶尔会很有用;但在这些场景之外,只需使用相对路径来引用当前目录中的内容。

【讨论】:

请不要在标记为ksh 的问题(另外)中使用shopt(仅限GNU bash),谢谢! @mirabilos 由于标签冲突或至少令人困惑,因此还不清楚 OP 在此处针对的是哪个 shell;但感谢您的评论;我将把它专门标记为仅限 Bash。 确实,很多shell问题都被标记了。在这种情况下,我通常使用子集或仅使用 POSIX sh,然后(如有必要)将特定于 shell 的子集作为附加组件之后。感谢您的考虑! 避免 nullglob 的常用方法是测试第一个参数是否存在。我通常写for x in *; do [[ -e $x ]] || break; dosomethingwith *; break; done之类的东西(一个循环只运行一次,ofc也可以使用&&)。在这里,你可以test -e "$1" 来捕捉它。 @mirabilos 好点;我已经包含了一个简短的段落。

以上是关于如何在使用“ls”命令以及将输出分配给变量时抑制标准错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何将命令的输出分配给 Makefile 变量

我正在使用来自 python 的 powershell 命令获取一些数据,而 Windows 中没有 cmd!如何将此输出分配给字符串变量?

在 Perl 中使用 nohup 时如何抑制系统输出?

如何在 Eclipse/PyDev 中抑制“未使用的变量”警告

如何执行 shell 命令,并将输出分配给 configure.ac 中的 AC_DEFINE 变量

将shell命令的输出分配给变量[重复]