次のコマンドを検討してください。
ls /mydir/*.txt | xargs chown root
意図は、mydir
内のすべてのテキストファイルの所有者をルートに変更することです
問題は、mydir
に.txt
ファイルがない場合、xargsはパスが指定されていないというエラーを表示することです。エラーがスローされるため、これは無害な例ですが、場合によっては、ここで使用する必要があるスクリプトのように、空のパスが現在のディレクトリであると想定されます。そのため、/home/tom/
からそのコマンドを実行すると、ls /mydir/*.txt
の結果がなく、/home/tom/
の下のすべてのファイルの所有者がルートに変更されます。
それでは、空の結果をxargsに無視させるにはどうすればよいですか?
GNU xargs
の場合、-r
または --no-run-if-empty
オプション:
--no-run-if-empty
-r
標準入力に空白が含まれていない場合は、コマンドを実行しないでください。通常、入力がない場合でもコマンドは1回実行されます。このオプションは、GNU拡張機能です。
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
man xargs
は--no-run-if-empty
。
xargs
に関しては、提案どおり-r
を使用できますが、BSD xargs
ではサポートされていません。
回避策として、次のような追加の一時ファイルを渡すことができます。
find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root $(mktemp)
または、その標準エラーを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
また、アクセス許可を変更する方法は適切ではなく、推奨されていません。間違いなく ls
command の出力を解析すべきではありません(参照: なぜlsの出力を解析すべきでないのか )。特に、コマンドをルートで実行している場合、ファイルはシェルによって解釈される可能性のある特殊文字で構成されたり、/
の周りにスペース文字があるファイルを想像できるため、結果がひどくなる可能性があります。
したがって、アプローチを変更し、代わりにfind
コマンドを使用する必要があります。
find /mydir -type f -name "*.txt" -execdir chown root {} ';'
OSX:-r
引数を処理するxargs
のBash再実装。 $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引数が受け入れられます(魔女は何もしません)。
私は個人的にスクリプトでlongoptsを使用することを好みますが、* BSD xargsはlongoptsを使用しないため、「-r」を使用するだけで、xargsはLinuxシステムの* BSDと同じように動作します
macOS(現在はMacOS Mojave)のxargsは、残念ながら "-r"引数をサポートしていません。