シェルスクリプトが空白またはその他の特殊文字で停止するのはなぜですか?


284

または、堅牢なファイル名処理およびシェルスクリプトで渡すその他の文字列の入門ガイド。

ほとんどの場合、うまく機能するシェルスクリプトを作成しました。ただし、一部の入力(たとえば、一部のファイル名)で停止します。

次のような問題が発生しました。

  • スペースを含むファイル名を持っていて、hello worldそれは2つの別個のファイルhelloworld
  • 2つの連続したスペースを持つ入力行があり、入力で1つに縮小しました。
  • 入力行から先頭と末尾の空白が消えます。
  • 入力に文字の1つが含まれている場合、\[*?実際にはファイルの名前であるテキストに置き換えられることがあります。
  • 入力にアポストロフィ'(または二重引用符")が含まれており、その時点以降は状況がおかしくなりました。
  • 入力にバックスラッシュがあります(または、Cygwinを使用していて、ファイル名の一部にWindowsスタイルの\区切り文字があります)。

何が起こっており、どうすれば修正できますか?


16
shellcheckプログラムの品質を改善するのに役立ちます。
aurelien

3
回答で説明されている保護技術に加えて、ほとんどの読者にはおそらく明らかですが、コマンドラインツールを使用してファイルを処理する場合は、空想的な文字を避けることをお勧めします可能であれば、そもそも名前。
bli


1
@bliいいえ、バグだけが現れるまでに時間がかかります。今日はバグを隠しています。そして今、あなたはあなたのコードで後で使用されるすべてのファイル名を知りません。
フォルカーシーゲル

最初に、パラメーターにスペースが含まれている場合は、コマンドラインで引用する必要があります。ただし、コマンドライン全体を取得して自分で解析することもできます。2つのスペースが1つのスペースに変わることはありません。任意の量のスペースがスクリプトに次の変数であることを伝えるため、「echo $ 1 $ 2」のような操作を行うと、スクリプトが間にスペースを1つ入れます。また、「find(-exec)」を使用して、forループではなくスペースでファイルを反復処理します。スペースをより簡単に処理できます。
パトリックテイラー

回答:


352

変数の置換とコマンドの置換には常に二重引用符を使用します:"$foo""$(foo)"

$foo引用符で囲まずに使用すると、スクリプトは$(foo)空白またはを含む入力またはパラメーター(または、コマンド出力)で停止します\[*?

そこで、読むのをやめることができます。さて、OK、さらにいくつかあります:

  • readビルトインを使用して1行readずつ入力を読み取るには、while IFS= read -r line; do …
    Plain read使用してバックスラッシュと空白を特別に処理します。
  • xargs避けてくださいxargs。使用する必要がある場合はxargs、それを作成しxargs -0ます。代わりにfind … | xargs好みfind … -exec …ます。
    xargs空白と文字を\"'特別に扱います。

この回答は、Bourne / POSIXスタイルのシェルに適用されます(shashdashbashkshmkshyash...)。Zshユーザーはこれをスキップして、二重引用符が必要な場合の終わりを読む必要がありますか?代わりに。全体の核心が必要な場合は、標準またはシェルのマニュアルをお読みください。


以下の説明には、いくつかの近似値が含まれていることに注意してください(ほとんどの条件に当てはまりますが、周囲のコンテキストまたは構成によって影響を受ける可能性があるステートメント)。

なぜ書く必要があるの"$foo"ですか?引用符がないとどうなりますか?

$foo「変数の値を取る」という意味ではありませんfoo。それはもっと複雑なことを意味します:

  • まず、変数の値を取得します。
  • フィールド分割:その値を空白で区切られたフィールドのリストとして扱い、結果のリストを作成します。変数が含まれている場合、例えば、foo * bar ​このステップの結果は、3つの要素のリストですfoo*bar
  • ファイル名の生成:各フィールドをグロブ、つまりワイルドカードパターンとして扱い、このパターンに一致するファイル名のリストに置き換えます。パターンがどのファイルとも一致しない場合、変更されません。この例では、これによりfoo、現在のディレクトリ内のファイルのリスト、最後にを含むリストが作成されますbar。現在のディレクトリが空の場合、結果はfoo*bar

結果は文字列のリストであることに注意してください。シェル構文には、リストコンテキストと文字列コンテキストの2つのコンテキストがあります。フィールドの分割とファイル名の生成はリストコンテキストでのみ行われますが、ほとんどの場合はそうです。二重引用符は、文字列コンテキストを区切ります。二重引用符で囲まれた文字列全体は、分割されない単一の文字列です。(例外:"$@"位置パラメーターのリストに展開すること。たとえば、3つの位置パラメーターがある場合"$@"と同等"$1" "$2" "$3"です。$ *と$ @の違いは何ですか?を参照してください

$(foo)またはを使用したコマンド置換でも同じことが起こり`foo`ます。サイドノートでは、使用しないでください`foo`:そのクォート規則は奇妙で移植性がなく、すべての最新のシェル$(foo)は直感的なクォート規則を除いて完全に同等です。

算術置換の出力にも同じ展開が行われますが、展開IFSできない文字のみが含まれているため、通常は問題になりません(数字やが含まれていないことを前提としています-)。

二重引用符が必要な場合を参照してください引用符を省略できる場合の詳細については。

このすべてのリマロールが発生することを意味する場合を除き、変数とコマンドの置換を常に二重引用符で囲むようにしてください。注意してください:引用符を省略すると、エラーだけでなくセキュリティホールにつながる可能性があります。

ファイル名のリストを処理するにはどうすればよいですか?

myfiles="file1 file2"ファイルを区切るスペースを使用してを記述した場合、スペースを含むファイル名では機能しません。Unixファイル名には、/(常にディレクトリ区切り文字である)およびnullバイト(ほとんどのシェルでシェルスクリプトで使用できない)以外の任意の文字を含めることができます。

と同じ問題myfiles=*.txt; … process $myfiles。これを行うと、変数myfilesには5文字の文字列が含まれ、ワイルドカードが展開さ*.txtれることを記述$myfilesします。スクリプトをに変更するまで、この例は実際に機能しますmyfiles="$someprefix*.txt"; … process $myfilessomeprefixがに設定されている場合final report、これは機能しません。

任意の種類(ファイル名など)のリストを処理するには、配列に入れます。これにはmksh、ksh93、yashまたはbash(またはzsh、これらのすべての引用の問題がない)が必要です。単純なPOSIXシェル(ashやdashなど)には配列変数がありません。

myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"

Ksh88には、異なる割り当て構文の配列変数がありますset -A myfiles "someprefix"*.txtksh88 / bashの移植性が必要場合は、異なるksh環境での割り当て変数を参照してください)。Bourne / POSIXスタイルのシェルには、単一の1つの配列があります。"$@"これは、設定した位置パラメーターの配列でありset、関数に対してローカルです。

set -- "$someprefix"*.txt
process -- "$@"

で始まるファイル名は-どうですか?

関連する注意事項として、ファイル名は-(ダッシュ/マイナス)で始まることができることに注意してください。ほとんどのコマンドはオプションを示すものとして解釈します。可変部分で始まるファイル名がある場合は、--上記のスニペットのように、その前に必ず渡すようにしてください。これは、コマンドの最後にオプションが到達したことを示すので、それ以降は、で始まっていてもファイル名-です。

または、ファイル名が以外の文字で始まっていることを確認できます-。絶対ファイル名はで始まり、相対名の先頭に/追加でき./ます。次のスニペットは、変数の内容を、fで始まらないことが保証されている同じファイルを参照する「安全な」方法に変えます-

case "$f" in -*) "f=./$f";; esac

このトピックに関する最後の注意事項では、一部のコマンドは---。という名前の実際のファイルを参照する必要がある場合-、またはそのようなプログラムを呼び出しており、stdinからの読み取りやstdoutへの書き込みを望まない場合は-、上記のように書き換えてください。「du -sh *」と「du -sh ./*」の違いをご覧くださいさらなる議論のために。

コマンドを変数に保存するにはどうすればよいですか?

「コマンド」とは、コマンド名(実行可能ファイルとしての名前、フルパスの有無にかかわらず、または関数、ビルトインまたはエイリアスの名前)、引数付きのコマンド名、またはシェルコードの3つを意味します。それに応じて、変数にそれらを保存するさまざまな方法があります。

コマンド名がある場合は、それを保存し、通常どおり二重引用符で変数を使用します。

command_path="$1"

"$command_path" --option --message="hello world"

引数付きのコマンドがある場合、問題は上記のファイル名のリストと同じです。これは文字列ではなく文字列のリストです。引数をスペースで区切った単一の文字列に詰め込むことはできません。その場合、引数の一部であるスペースと引数を区切るスペースの違いを区別できないためです。シェルに配列がある場合は、それらを使用できます。

cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

配列なしのシェルを使用している場合はどうなりますか?それらを変更することを気にしない場合は、引き続き位置パラメータを使用できます。

set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"

リダイレクトやパイプなどの複雑なシェルコマンドを保存する必要がある場合はどうなりますか?または、位置パラメータを変更したくない場合は?次に、コマンドを含む文字列を作成し、eval組み込みコマンドを使用できます。

code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"

定義のネストされた引用符に注意してくださいcode:単一引用符'…'は文字列リテラルを区切るので、変数の値はcodestring /path/to/executable --option --message="hello world" -- /path/to/file1です。eval組み込みは、それがスクリプトに登場したかのように、引数として渡された文字列を解析するためにシェルに指示しますので、その時点での引用符やパイプ等、解析され

使用にevalは注意が必要です。いつ解析されるかについて慎重に考えてください。特に、コードにファイル名を詰め込むことはできません。ソースコードファイルにある場合と同様に、引用符で囲む必要があります。それを行う直接的な方法はありません。何かのようにcode="$code $filename"ファイル名が任意のシェルの特殊文字が含まれている場合改行(空白を、$;|<>、など)。code="$code \"$filename\""まだ壊れてい"$\`ます。でも、code="$code '$filename'"ファイル名が含まれている場合は壊れます'。2つの解決策があります。

  • ファイル名の周りに引用符のレイヤーを追加します。それを行う最も簡単な方法は、その周りに一重引用符を追加し、一重引用符をで置き換えること'\''です。

    quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
    code="$code '${quoted_filename%.}'"
  • コード内に変数展開を保持し、コードフラグメントが構築されたときではなく、コードが評価されたときに参照されるようにします。これは簡単ですが、コードがループで構築されている場合などではなく、コードが実行されたときに変数が同じ値である場合にのみ機能します。

    code="$code \"\$filename\""

最後に、コードを含む変数が本当に必要ですか?コードブロックに名前を付ける最も自然な方法は、関数を定義することです。

どうしたread

なし-rread、継続行を許可します—これは入力の単一の論理行です:

hello \
world

read入力行をで文字で区切られたフィールドに分割します$IFS-r、バックスラッシュもこれらをエスケープします)。入力三個の単語を含む行である場合、例えば、次にread first second third設定first、入力の最初の単語にsecond第2ワードおよびthird第三のワード。さらに単語がある場合、最後の変数には、前の単語を設定した後に残っているすべてが含まれます。先頭および末尾の空白は削除されます。

IFS空の文字列に設定すると、トリミングが回避されます。「IFS =ではなく、なぜIFS = readが頻繁に使用されるのか」を参照してください読みながら..`?より長い説明のため。

何が問題なのxargsですか?

の入力形式xargsは、空白で区切られた文字列で、オプションで単一引用符または二重引用符で囲むことができます。この形式を出力する標準ツールはありません。

xargs -L1またはへの入力xargs -lはほとんど行のリストですが、完全ではありません—行の終わりにスペースがある場合、次の行は継続行です。

xargs -0該当する場所(および利用可能な場所:GNU(Linux、Cygwin)、BusyBox、BSD、OSX を使用できますが、POSIXにはありません)。nullファイルはほとんどのデータ、特にファイル名には表示されないため、これは安全です。ファイル名のヌル区切りリストを作成するには、を使用しますfind … -print0(または、find … -exec …以下で説明するように使用できます)。

で見つかったファイルを処理するにはどうすればよいfindですか?

find  -exec some_command a_parameter another_parameter {} +

some_command外部コマンドである必要があります。シェル関数またはエイリアスにすることはできません。ファイルを処理するためにシェルを呼び出す必要がある場合は、sh明示的に呼び出します。

find  -exec sh -c '
  for x do
    … # process the file "$x"
  done
' find-sh {} +

他に質問があります

ブラウズこのサイト上のタグを、またはまたは。(「詳細...」をクリックすると、一般的なヒントと一般的な質問の選択リストが表示されます。)検索して回答が見つからない場合は、質問してください


6
@ John1024これはGNU機能のみであるため、「標準ツールなし」に固執します。
ジル

2
また、前後に引用符必要がある$(( ... ))(も$[...]中を除いて、いくつかのシェル中)zsh(偶数のshエミュレーション)とをmksh
ステファンシャゼル

3
xargs -0POSIX ではないことに注意してください。FreeBSDを除き、xargs一般的xargs -r0にはxargs -0
ステファンシャゼル

2
@ John1024、いいえ、ls --quoting-style=shell-alwaysと互換性がありませんxargs。試してみてくださいtouch $'a\nb'; ls --quoting-style=shell-always | xargs
ステファンチャゼラス

3
もう一つの素敵な(GNU-のみ)機能がありxargs -d "\n"ますが、例えば実行できるようにlocate PATTERN1 |xargs -d "\n" grep PATTERN2一致するファイル名を検索するPATTERN1コンテンツマッチングとPATTERN2を。GNUがなければ、あなたはそれを行うことができます例えばのようなlocate PATTERN1 |perl -pne 's/\n/\0/' |xargs -0 grep PATTERN1
アダム・カッツ

26

ジルの答えは優れていますが、私は彼の主要な点で問題を取り上げます

変数の置換とコマンドの置換には常に二重引用符を使用します: "$ foo"、 "$(foo)"

単語の分割を行うBashのようなシェルを使用する場合は、もちろん安全なアドバイスは常に引用符を使用することです。ただし、単語分割は常に実行されるとは限りません

§単語分割

これらのコマンドはエラーなしで実行できます

foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
case $foo in bar) echo bar ;; baz) echo baz ;; esac

ユーザーにこの動作を採用することを勧めていませんが、単語の分割がいつ発生するかを誰かがしっかりと理解していれば、引用符を使用するタイミングを自分で決定できるはずです。


19
私の答えで言及したように、詳細についてはunix.stackexchange.com/questions/68694/…を参照してください。「なぜシェルスクリプトが詰まるのですか?」という質問に注目してください。最も一般的な問題(このサイトや他の場所での長年の経験から)は、二重引用符が欠落していることです。「常に二重引用符を使用する」は、「二重引用符を常に使用する必要がある場合を除き、常に二重引用符を使用する」よりも覚えやすいです。
ジル

14
初心者にとってルールを理解することは困難です。たとえば、foo=$bar大丈夫ですがexport foo=$bar、そうでenv foo=$varはありません(少なくとも一部のシェルでは)。初心者へのアドバイス:自分が何をしているかを知らず、そうしない正当な理由がない限り、常に変数を引用してください
ステファンシャゼル

5
@StevenPennyそれは本当に正しいですか?引用符がスクリプトを壊す合理的なケースはありますか?半分の場合には引用符を使用する必要があり、他の半分の引用符オプションで使用できる状況では、「常に念のため引用符を使用する」という推奨事項が考えられるべきです。そのような例外のリストを初心者に教えることは、必要な/不要な引用を混乱させ、スクリプトを破壊し、さらに学習するように動機づけるので、効果的ではなく(コンテキストを欠いているため、覚えていません)、逆効果であることがよく知られています。
ペティス

6
私の0.02ドルは、すべてを引用することを勧めるのが良いアドバイスだと思います。誤って引用する必要のないものを引用することは無害であり、誤って引用する必要のないものを引用するのは有害です。そのため、正確に単語の分割が発生する複雑さを理解できないシェルスクリプト作成者の大多数にとって、すべてを引用することは、必要な場合にのみ引用するよりもはるかに安全です。
godlygeek

5
@Peteris and godlygeek:「引用符がスクリプトを破壊する合理的なケースはありますか?」「合理的」の定義に依存します。スクリプトが設定した場合はcriteria="-type f"、その後、find . $criteria動作しますが、find . "$criteria"しません。
Gマン14

22

私の知る限りでは、それは二重引用符の展開に必要であり、これらの例は、2つの特別なシェルパラメーター関与する唯一の2例がある"$@""$*"二重引用符で囲まれたときに違った展開して指定されています- 。他のすべての場合(おそらく、シェル固有の配列実装を除く)では、展開の動作は構成可能なものです-そのためのオプションがあります。

もちろん、これは二重引用符を避けるべきだということではありません-それどころか、シェルが提供しなければならない展開を区切る最も便利で堅牢な方法でしょう。しかし、代替案はすでに専門的に詳しく説明されているので、シェルが値を展開したときに何が起こるかを議論するのに最適な場所だと思います。

シェルは、そのような心と魂で(そのようなものを持っている人にとって)コマンドインタープリターです。これは、大きくてインタラクティブなパーサーsedです。シェルステートメントが空白などで窒息している場合は、シェルの解釈プロセス、特に入力ステートメントをアクション可能なコマンドに変換する方法と理由を完全に理解していないためです。シェルの役割は次のとおりです。

  1. 入力を受け入れる

  2. 正しく解釈し、トークン化された入力語に分割ます

    • 入力は、$wordまたはなどのシェル構文項目です。echo $words 3 4* 5

    • 単語は常に空白で分割されます-それは単なる構文です-しかし、リテラルの空白文字のみがその入力ファイルでシェルに提供されます

  3. 必要に応じてそれらを複数のフィールドに展開します

    • フィールド単語の展開から生じる-最終的な実行可能なコマンドを構成する

    • "$@"$IFS フィールド分割、およびパス名展開を除き、入力は常に単一のフィールドに評価される必要があります

  4. そして、結果のコマンドを実行します

    • ほとんどの場合、これには何らかの形で解釈の結果を渡すことが含まれます

多くの場合、シェルは接着剤であると言われますが、これが真実である場合、固着しているのは、プロセスの引数またはフィールドのリストですexec。ほとんどのシェルはNULバイトをうまく処理しません-たとえあったとしても-これはすでにバイトを分割しているためです。シェルにはexec 多くのことが必要でありNUL、システムカーネルに渡される引数の区切られた配列を使用してこれを行う必要がありますexec。シェルの区切り文字と区切りデータを混在させると、シェルはおそらくそれを台無しにします。その内部データ構造は、ほとんどのプログラムと同様に、その区切り文字に依存しています。zsh、特に、これを台無しにしません。

そして、それはどこに$IFS来るのです$IFSか。シェルが単語からフィールドにどのようにシェル展開を分割するかを定義するシェルパラメータ-特にそれらのフィールドが区切る値に-は常に存在し、同様に設定可能です。- $IFS以外の区切り文字でシェル拡張を分割します。NULつまり、シェルは、内部データ配列の$IFSwith の値に一致する拡張の結果のバイトを置き換えますNUL。このように見ると、すべてのフィールド分割シェル展開が-$IFS区切りのデータ配列であることがわかるかもしれません。

既に区切られていない展開$IFSのみを区切ることを理解することが重要です-これは二重引用符で行うことができます。展開を引用するときは、値の先頭と少なくとも末尾で区切ります。これらの場合、分離するフィールドがないため適用されません。実際、二重引用符で囲まれた展開は、空の値に設定されている場合、引用符で囲まれていない展開と同じフィールド分割動作を示します。"$IFSIFS=

引用されていない限り、$IFSそれ自体が$IFS区切られたシェル拡張です。デフォルトでは、指定された値に設定されます-3 <space><tab><newline>つすべてに含まれる場合、特別なプロパティを示します$IFS。以下のための任意の他の値が、一方$IFS、単一に評価するために指定されたフィールドの拡張あたり発生$IFS 空白 -これら3つのいずれかが-拡張ごとに単一のフィールドにElideのために指定された配列およびリーディング/トレーリング配列が完全に省略されています。これはおそらく例を通して理解するのが最も簡単でしょう。

slashes=///// spaces='     '
IFS=/; printf '<%s>' $slashes$spaces
<><><><><><     >
IFS=' '; printf '<%s>' $slashes$spaces
</////>
IFS=; printf '<%s>' $slashes$spaces
</////     >
unset IFS; printf '<%s>' "$slashes$spaces"
</////     >

しかし、それはただ$IFS- 質問されたとおりの単語分割または空白だけなので、特殊文字は何ですか?

シェルは-デフォルトで-引用符で囲ま?*[ていない特定のトークンここに記載されているトークンなどがリストに現れると、複数のフィールドに展開されます。これは、パス名展開、またはグロビングと呼ばれます。これは非常に便利なツールであり、シェルの解析順序でフィールドを分割した後に発生するため、$ IFSの影響を受けません- パス名の展開によって生成されたフィールドは、ファイル名自体の先頭/末尾で区切られますそれらのコンテンツには、現在にある文字が含まれてい$IFSます。この動作はデフォルトでオンに設定されていますが、それ以外の場合は非常に簡単に構成できます。

set -f

これは、シェルにグロブしないように指示します。パス名の展開は、少なくとも現在のシェルが別の新しいシェルプロセスに置き換えられた場合など、その設定が何らかの形で取り消されるまで発生しません。

set +f

...シェルに発行されます。二重引用符は、$IFS フィールド分割の場合と同様に、このグローバル設定を展開ごとに不要にします。そう:

echo "*" *

...パス名の展開が現在有効になっている場合、引数ごとに非常に異なる結果が生成される可能性があります-最初のものはリテラル値(つまり、単一のアスタリスク文字)のみに展開し、2番目は同じ現在の作業ディレクトリに一致する可能性のあるファイル名が含まれていない場合(および、ほぼすべてのファイルに一致する場合)。ただし、次の場合:

set -f; echo "*" *

...両方の引数の結果は同じです- *その場合、展開しません。


実際、@StéphaneChazelasには、それが(ほとんど)助けること以上に物事を混乱させることに同意します... IFS実際にどのように機能するかについてのより良いアイデア(およびいくつかの例)があります。私は何をしていない、なぜ取得することで、これまでに設定することをお勧めすることがIFSデフォルト以外の何かに。
ワイルドカード

1
@Wildcard-フィールドの区切り文字です。変数に複数のフィールドに展開する値がある場合、それを分割し$IFSます。その後、cd /usr/bin; set -f; IFS=/; for path_component in $PWD; do echo $path_component; done印刷します。最初のフィールドは空のため、空のフィールドです。path_componentsには改行やスペースなどを含めることができます-コンポーネントはデフォルト値ではなく分割されているため、問題ではありません。とにかく人々はいつもそれをやる。あなたのシェルもそれを行う\nusr\nbin\necho//awk
-mikeserv

3

ファイル名にスペースがあり、ディレクトリ名にスペースがある大きなビデオプロジェクトがありました。一方でfind -type f -print0 | xargs -0、いくつかの目的のために、別のシェルにまたがって動作しますが、私はあなたがbashのを使用している場合は、カスタムIFS(入力フィールドセパレータ)を使用すると、あなたに多くの柔軟性を与えることを見つけます。以下のスニペットはbashを使用し、IFSを単なる改行に設定します。ファイル名に改行が含まれていない場合:

(IFS=$'\n'; for i in $(find -type f -print) ; do
    echo ">>>$i<<<"
done)

IFSの再定義を分離するために括弧を使用することに注意してください。IFSを回復する方法に関する他の投稿を読みましたが、これは簡単です。

さらに、IFSを改行に設定すると、シェル変数を事前に設定して、簡単に出力できます。たとえば、改行をセパレータとして使用して、変数Vを増分的に増やすことができます。

V=""
V="./Ralphie's Camcorder/STREAM/00123.MTS,04:58,05:52,-vf yadif"
V="$V"$'\n'"./Ralphie's Camcorder/STREAM/00111.MTS,00:00,59:59,-vf yadif"
V="$V"$'\n'"next item goes here..."

それに応じて:

(IFS=$'\n'; for v in $V ; do
    echo ">>>$v<<<"
done)

これでecho "$V"、二重引用符を使用して改行を出力し、Vの設定を「リスト」できます。(説明については、このスレッドへのクレジット$'\n'。)


3
ただし、改行またはグロブ文字を含むファイル名にはまだ問題があります。関連項目:なぜfindの出力をループするのは悪い習慣ですか?。を使用する場合zsh、使用IFS=$'\0'して使用できます-print0zsh展開時にグロビングを行わないため、グロブ文字は問題になりません)。
ステファンシャゼラス

1
これは、スペースを含むファイル名で機能しますが、潜在的に敵意のあるファイル名や偶然の「無意味な」ファイル名に対しては機能しません。を追加することで、ワイルドカード文字を含むファイル名の問題を簡単に修正できますset -f。一方、改行を含むファイル名では、アプローチが根本的に失敗します。ファイル名以外のデータを扱う場合、空のアイテムでも失敗します。
ジル

右、私の注意点は、ファイル名の改行では動作しないということです。しかし、私たちはただ狂気の恥ずかしがり屋の線を引く必要があると信じています;
ラス

そして、なぜこれが下票を受け取ったのか分かりません。これは、スペースを含むファイル名を反復処理するための完全に合理的な方法です。-print0を使用するにはxargsが必要であり、そのチェーンを使用するのが難しいことがあります。誰かが私の答えに同意しないのは残念ですが、それはそれを支持する理由ではありません。
ラス

0

上記のすべてのセキュリティへの影響を考慮し、展開する変数を信頼し、制御できると仮定すると、空白を使用して複数のパスを使用することができますeval。しかし、注意してください!

$ FILES='"a b" c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
$ FILES='a\ b c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.