シェル(zsh / bash)での拡張と引用をよく理解していません。

シェル(zsh / bash)での拡張と引用をよく理解していません。

シェルから変数を引用するための答えをここで読みました。基本的にこれは私が実行するものです。

for f in "$(tmsu files)"; do echo "${f}"; done

これは予想される結果です:ファイルのリスト。しかし、私はそれらが1つの文字列として現れると確信しています。少し読んだ後、これがzsh-ismであることに気づきました。これは、ほとんどのシェルとは異なる方法で分割を処理します。

bashだから私は同じコマンドを実行して実行しました。リストが分割されると思いましたが、残念ながら正確に同じ結果。だから今は本当に混乱しています。

  1. Bashが期待どおりに実行されないのはなぜですか?
  2. zsh分割線を作成する方法は?

私は(zsh)を試しましたが成功しなかったfor f in "$(tmsu files)"; do echo "${=f}"; done ので for f in "$(=tmsu files)"; do echo "${f}"; done 、分割方法を誤解したようですzsh

さらに悪いことは、ある時点で私が望むように正確に動作するようにしましたが、どうしたのか覚えていないということです。

答え1

for f in "$(tmsu files)"; do echo "${f}"; done

これはzsh-ismよりも悪い習慣です。二重引用符は、拡張結果を単一の文字列として保持するようにシェルに指示します。ほとんどの場合、これはあなたが望むものです。たとえば、次のような場合があります。

filename="foo bar"
ls "$filename"

2つの引数lsの合計ではなく単一の引数でファイル名を取得しようとしています。 (これらは別のファイル名になります。)これで、コマンド置換を使用しているという事実はこれを変更しません。この場合、引用が問題を解決できないことは正しいですが、この構成は最初は厄介です。 (もちろん、POSIXシェルにはzshに独自のツールがあります)。foobar

次のファイルのリストを検討してください。

hello.txt
some silly name with spaces.txt

を使用すると、2つで"$(output filenames)"はなく1つの文字列が提供されます。
を使用すると、$(output filenames)2つではなく6つの文字列が提供されます。

IFS後者が改行文字に分割され、2つのファイル名を提供するように改行文字のみを設定することでこの問題を解決できます。ただし、これは面倒でグローバルな影響を与え、set -fファイル名が変更されないようにするには、まだワイルドカードを無効にする必要がありますfunny * name.txt

(これまでは、変数拡張を分割しなくても引用符なしのコマンド置換結果を分割する点で、デフォルトではzshと同じですが、デフォルトでは結果をワイルドカードとして表示しません。)

違いを見ることができない理由は、echo取得したすべての引数を単一のスペースで連結して印刷するためです。したがって、まったく同じ出力を生成しますecho foo barecho "foo bar"代わりに、次のようなものを使用すると、printf "<%s>\n" ...パラメータの境界をより明確に表示できます。

$ printf "<%s>\n" foo bar
<foo>
<bar>
$ printf "<%s>\n" "foo bar"
<foo bar>

また見なさい:

答え2

しかし、私はそれらが1つの文字列として現れると確信しています。

これが引用符を使用すれば得ることができるのです。zshこれを行うにはどうすればわかりませんが、マニュアルにはbash次のように記載されています(「拡張」の下の「単語の分離」セクション)。

パラメータ拡張、コマンド置換、および算術拡張のシェルスキャン結果二重引用符内には表示されません。単語の分割に使用されます。

(強調追加)。一重引用符では単語の区切りは発生しませんが、その中でも置換や拡張は行われません。

コマンドの置換に別々のパラメータを使用するには、引用符を削除する必要があります。ただし、tmsuスペースを含む結果が生成されると、不要な分割が生成され(スペースと改行の両方が単語区切り文字と見なされるため)、状況はより複雑になります。では、1行に1つの結果を提供するとbash仮定すると、次のことができます。tmsu

tmsu files |
    while IFS= read -r f
    do
        printf "<%s>\n" "$f"
    done

組み込みread関数は入力の行全体を読み込みます。名前はいくらでも指定できます。最後の文字を除くすべての文字は単語区切り記号(デフォルトでは空白)に基づいて割り当てられ、最後の文字は行の残りの部分に割り当てられているため、拡張と置換の間の空白処理の面倒bashを解決するのに最適なアイデアです。

答え3

  1. zsh分割線を作成する方法は?

f引数拡張フラグを使用します(詳細についてはを参照してください。次のman zshexpnように言いますps:\n:

for f in ${(f)"$( tmsu files )"}; do
  print -ru2 -- $f
done

パイプデータを印刷するためにstdoutを出て、出力をstderrに送信します(おそらく出力を印刷するよりも多くのことがあります)。

ツールがASCIIヌル文字(別名NUL、\ 0)で区切られた出力を生成できる場合、zshはその出力を分割する代わりにフラグ0(省略)を使用できます。ps:\0:f

空のレコードを保存するには、@フラグと"引用符(たとえば"${(@f)"$( command )"}")を追加します。コマンド置換の性質のため、末尾の改行を検索することは不可能です。

readループで使用するには、whilezshまたはbashで次の操作が機能する必要があります。

while IFS= read -ru3 -d '' x || [[ $x ]]; do
  {
    printf >&2 '%s\n' "$x"
    z=$x
  } 3<&-
done 3< <(printf 'x\0y\0z')
printf 'z: %s\n' "$z"

フィールド分割やバックスラッシュの解釈がなく、ループがサブシェルで実行されず、stdinを引き続き使用でき、末尾の区切り文字がなくても最後の要素を見つけることができます。

関連情報