web-dev-qa-db-ja.com

xargsのパイプからの出力を適切にエスケープする

例:

% touch -- safe-name -name-with-dash-prefix "name with space" \
    'name-with-double-quote"' "name-with-single-quote'" \
    'name-with-backslash\'

xargsは二重引用符を処理できないようです:

% ls | xargs ls -l 
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
ls: invalid option -- 'e'
Try 'ls --help' for more information.

-0オプション、ダッシュプレフィックスが付いた名前に問題があります。

% ls -- * | xargs -0 -- ls -l --
ls: invalid option -- 'e'
Try 'ls --help' for more information.

これは、改行、制御文字などの他の潜在的に問題のある文字を使用する前です。

5
Gerry Lufwansa

POSIX仕様 は、その例を示しています。

ls | sed -e 's/"/"\\""/g' -e 's/.*/"&"/' | xargs -E '' printf '<%s>\n'

(ファイル名はbytesの任意のシーケンスです(/およびNULL以外)およびsed/xargstextが必要ですが、ロケールをCに修正して(NUL以外のすべてのバイトが有効な文字になる場合)、信頼性を高める必要があります(引数の最大長に非常に低い制限があるxargs実装を除く))

-E ''は、一部のxargs実装で必要です。この実装がない場合、_引数を理解して入力の終わりを示します(echo a _ b | xargsaを出力します(例のみ)。

GNU xargsでは、以下を使用できます。

ls | xargs -d '\n' printf '<%s>\n'

GNU xargsには、他のいくつかの実装によってコピーされた-0もあるので、

ls | tr '\n' '\0' | xargs -0 printf '<%s>\n'

少しポータブルです。

これらはすべて、ファイル名に改行文字が含まれていないことを前提としています。改行文字を含むファイル名がある場合、lsの出力は後処理できません。あなたが取得する場合:

a
b

これは、2つのaファイルとbファイル、またはa<newline>bという名前のファイルのいずれかである可能性があり、知る方法はありません。

GNU lsには--quoting-style=Shell-alwaysがあり、出力を明確にして後処理できる可能性がありますが、引用はxargsが期待する引用と互換性がありません。 xargsは、引用の"..."\x'...'形式を認識します。ただし、"..."'...'はどちらも強い引用符であり、改行文字を含めることはできません(\のみがxargsの改行文字をエスケープできます)。そのため、shの引用と互換性がありません。ここで、'...'だけが強い引用符です(改行文字を含めることができます)が、\<newline>は、エスケープされた改行ではなく、行連結(削除されます)です。

シェルを使用してその出力を解析し、xargsで予期される形式で出力することができます。

eval "files=($(ls --quoting-style=Shell-always))"
[ "${#files[#]}" -eq 0 ] || printf '%s\0' "${files[@]}" |
  xargs -0 printf '<%s>\n'
7

xargs-0のnull区切りの入力オプションを理解するには、送信側も送信するデータにnull区切り文字を適用する必要があります。

それ以外の場合、2つの間に同期はありません。

1つのオプションは、そのような区切り文字を配置できるGNU findコマンドです。

find . -maxdepth 1 ! -name . -print0 | xargs -0 ls -ld
4
user218374

あなたが言ったように、xargs-0を使用しない限り、一致しない二重引用符を好まないが、-0はnullで終了するデータをフィードする場合にのみ意味があります。したがって、これは失敗します:

$ echo * | xargs
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
name-with-backslash -name-with-dash-prefix

しかし、これはうまくいきます:

$ printf '%s\0' -- * | xargs -0
-- name-with-backslash\ -name-with-dash-prefix name-with-double-quote" name-with-single-quote' name with space safe-name

いずれにしても、あなたの基本的なアプローチはこれを行うための本当に最良の方法ではありません。 xargslsをいじるのではなく、代わりにシェルグロブを使用します。

$ for f in *; do ls -l -- "$f"; done
-rw-r--r-- 1 terdon terdon 4142 Aug 11 16:03 a
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-backslash\'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 -name-with-dash-prefix
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-double-quote"'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 "name-with-single-quote'"
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name with space'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 safe-name
3
terdon

コマンドの出力を解析するls つまり 解析するように設計されていないis複数の文字を処理するように設計されていません (例:new linesおよび{})シェル自体がそれを行う場合:

set -- *; for f; do echo "<$f>"; done

set    -- *
for    f
do     ls "$f"
done

または、1つのコマンドラインで:

$ set -- *; for f; do echo "<$f>"; done
<name-with-backslash\>
<-name-with-dash-prefix>
<name-with-double-quote">
<name-with-single-quote'>
<name with space>
<safe-name>
<with_a
newline>

出力では改行が完全に問題なく処理されます(最後のファイル名としてnの例があります)。

または、ファイルの数によってシェルが遅くなる場合は、findを使用します。

$ find ./ -type f -exec echo '<{}>' \;
<./safe-name>
<./with_a
newline>
<./name-with-double-quote">
<./-name-with-dash-prefix>
<./name with space>
<./name-with-single-quote'>
<./name-with-backslash\>

Findは、シェルとは異なり、すべてのドットファイルとすべてのサブディレクトリを処理することに注意してください。

0
Isaac