如何忽略xargs命令如果stdin输入是空的?

考虑以下命令:

ls /mydir/*.txt | xargs chown root

其目的是将mydir中所有文本文件的所有者更改为root

问题是,如果在mydir中没有.txt文件,那么xargs会显示一个错误,说没有指定路径。这是一个无害的例子,因为会抛出一个错误,但在某些情况下,比如在我需要在这里使用的脚本中,会假设一个空白路径是当前目录。因此,如果我从/home/tom/运行该命令,那么如果ls /mydir/*.txt没有结果,并且/home/tom/下的所有文件都将其所有者更改为root。

如何让xargs忽略空结果呢?

61760 次浏览

man xargs表示--no-run-if-empty

对于GNU xargs,你可以使用-r--no-run-if-empty选项:

< p > --no-run-if-empty < br > -r
如果标准输入中不包含非空字符,则不执行该命令。正常情况下,即使没有输入,该命令也只执行一次。该选项是一个GNU扩展

在macOS上使用的BSD版本的xargs不为空输入运行命令,因此不需要-r选项(该版本的xargs也不接受它)。

非gnu xargs的用户可以利用-L <#lines>-n <#args>-i-I <string>:

ls /empty_dir/ | xargs -n10 chown root # chown executed every 10 args or fewer
ls /empty_dir/ | xargs -L10 chown root # chown executed every 10 lines or fewer
ls /empty_dir/ | xargs -i cp {} {}.bak # every {} is replaced with the args from one input line
ls /empty_dir/ | xargs -I ARG cp ARG ARG.bak # like -i, with a user-specified placeholder

请记住,xargs在空格处分隔行,但可以使用引号和转义;RTFM的详细信息。

此外,正如Doron Behar提到的,这个解决方案是不可移植的,所以可能需要检查:

$ uname -is
SunOS sun4v
$ xargs -n1 echo blah < /dev/null
$ uname -is
Linux x86_64
$ xargs --version | head -1
xargs (GNU findutils) 4.7.0-git
$ xargs -n1 echo blah < /dev/null
blah

对于xargs,你可以按照建议使用-r,但是BSD xargs不支持它。

所以作为解决方案,你可以传递一些额外的临时文件,例如:

find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root $(mktemp)

或将其stderr重定向为null (2> /dev/null)。

find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root 2> /dev/null || true

另一种更好的方法是使用while循环遍历找到的文件:

find /mydir -type f -name "*.txt" -print0 | while IFS= read -r -d '' file; do
chown -v root "$file"
done

参见:在Mac OS X中忽略xargs的空结果


还请注意,您更改权限的方法不是很好,不鼓励这样做。当然你不应该解析ls命令的输出(参见:为什么不应该解析ls的输出)。特别是当您以root用户运行命令时,因为您的文件可能包含特殊字符,这些字符可能会被shell解释,或者想象文件在/周围有一个空格字符,那么结果可能会很糟糕。

因此,你应该改变你的方法,转而使用find命令。

find /mydir -type f -name "*.txt" -execdir chown root {} ';'

在OSX: Bash重新实现xargs处理-r参数时,将其置于例如$HOME/bin中,并将其添加到PATH:

#!/bin/bash
stdin=$(cat <&0)
if [[ $1 == "-r" ]] || [[ $1 == "--no-run-if-empty" ]]
then
# shift the arguments to get rid of the "-r" that is not valid on OSX
shift
# wc -l return some whitespaces, let's get rid of them with tr
linecount=$(echo $stdin | grep -v "^$" | wc -l | tr -d '[:space:]')
if [ "x$linecount" = "x0" ]
then
exit 0
fi
fi


# grep returns an error code for no matching lines, so only activate error checks from here
set -e
set -o pipefail
echo $stdin | /usr/bin/xargs $@

这是GNU xargs的一种行为,可以使用-r,——no-run-if-empty来抑制。

xargs的*BSD变体默认有这种行为,所以不需要-r。自FreeBSD 7.1(2009年1月发布)以来,出于兼容性原因,-r参数被接受(witch什么都不做)。

我个人更喜欢在脚本中使用longopts,但由于*BSD xargs不使用longopts,只需使用“-r”,xargs在*BSD和linux系统上的作用相同

不幸的是,MacOS(目前MacOS Mojave)上的xargs不支持“-r”参数。

使用-r/--no-run-if-empty xargs参数的跨平台(Mac和Linux)替代方案:

参数为空的示例(在Ubuntu 18.04和Big Sur上的结果相同):

$ echo | xargs -I {}  echo "This is a test with '{}'"
$
$
$ cat /dev/null | xargs -I {}  echo "This is a test with '{}'"
$

多行示例:

$ seq 1 5  | xargs -I {}  echo "This is a test with '{}'"
This is a test with '1'
This is a test with '2'
This is a test with '3'
This is a test with '4'
This is a test with '5'
$

删除所有本地引用,其中以前的远程跟踪分支现在已经消失

#!/bin/sh
LC_ALL=C git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ |\
sed -n '/ \[gone\]$/{s@@@;p}' |\
xargs -I% git branch --delete %
  1. LC_ALL=C,通过将语言设置为英语来避免本地化问题
  2. 匹配以 [gone]结尾的for-each-ref输出并接受refname
  3. 修剪那些局部引用(将--delete改为-D以强制)

没有bashisms,应该在BSD/GNU中工作