
シェルから変数を引用するための答えをここで読みました。基本的にこれは私が実行するものです。
for f in "$(tmsu files)"; do echo "${f}"; done
これは予想される結果です:ファイルのリスト。しかし、私はそれらが1つの文字列として現れると確信しています。少し読んだ後、これがzsh-ismであることに気づきました。これは、ほとんどのシェルとは異なる方法で分割を処理します。
bash
だから私は同じコマンドを実行して実行しました。リストが分割されると思いましたが、残念ながら正確に同じ結果。だから今は本当に混乱しています。
- Bashが期待どおりに実行されないのはなぜですか?
- 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に独自のツールがあります)。foo
bar
次のファイルのリストを検討してください。
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 bar
。echo "foo bar"
代わりに、次のようなものを使用すると、printf "<%s>\n" ...
パラメータの境界をより明確に表示できます。
$ printf "<%s>\n" foo bar
<foo>
<bar>
$ printf "<%s>\n" "foo bar"
<foo bar>
また見なさい:
- 噴射Gregのwikiから
- 「for」のある行を読んでみてはいかがでしょうか?
- ファイルの行を繰り返す方法は?(「ファイル行」と呼ばれていますが、コマンド置換にその内容やその他の内容があるかどうかは重要ではありません
cat
。) 常にそうであるように、次のようになります。 - スペースやその他の特殊文字が原因でシェルスクリプトが停止するのはなぜですか?
答え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
- 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
ループで使用するには、while
zshまたは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を引き続き使用でき、末尾の区切り文字がなくても最後の要素を見つけることができます。