bash 変数を「展開」する方法 (含まれているコードは bash では機能しますが、zsh では機能しません)

bash 変数を「展開」する方法 (含まれているコードは bash では機能しますが、zsh では機能しません)

次のような答えこれパスをコントロールする方法をうまく説明している全て変数をコマンドに渡します。

これを引数ごとに行う方法を調べたいと思いました。次の点に注意してください (これは でテストされましたzsh)。

$ program() { echo 1: $1 2: $2 3: $3; }

$ run() { program "$@"; }

$ run2() { echo `run $1`; }

$ run2 'a b' c
1: a b 2: 3:

スペースを含む文字列である最初の引数を渡し、その連結された$@スタイルを別のコマンドに渡す方法が必要です。たとえば、次のようにrun2 "a b" c生成されます。1: a 2: b 3:

この時点で、当面の問題は解決しました。テスト コードを zsh でテストすると壊れますが、実際の bash スクリプトに実装すると動作するからです。

これは、文字列と引数の処理における「安全」ではない複雑な部分に依存している可能性があることを示しています。したがって、これは、この動作を確実に実現するためのより堅牢な方法についてのコメントを求めるものです。

答え1

run2ここで重要な bash と zsh の違いは、の呼び出し方法run、具体的には引用符を付けずに残す効果にあります$1

  • zsh では、run $1に「空の場合は削除」演算子を適用します$1。つまり、runに渡された最初の引数で を呼び出しますrun2。ただし、 の最初の引数run2が空だった場合 (または がrun2引数なしで呼び出された場合) は、 がrun引数なしで呼び出されます。
  • bash などの他の Bourne スタイルのシェルでは、run $1に「split+glob」演算子を適用します$1。つまり、 の最初の引数をrun2空白で区切られたチャンク¹に分割し、各部分をワイルドカード パターン² として解釈し、1 つ以上のファイルに一致する各ワイルドカード パターンを一致リストで置き換えます。

したがって、 zsh では引数を使用してrun2 'a b' c呼び出します(引数は変更されずに渡されます) が、bash では 2 つの引数を使用して呼び出します(空白で区切られた部分に分割されます)。runa brunab

スペースを含む文字列である最初の引数を渡し、その連結された$@スタイルを別のコマンドに渡す方法が必要です。たとえば、次のようにrun2 "a b" c生成されます。1: a 2: b 3:

説明と例では異なることが書かれています。最初の引数を他のコマンドに渡す場合は、run "$1"引数が分割されないようにするために を使用してください。引数を変更せずに渡すことが のポイントです"$@"

実際にやりたいことは、最初の引数を空白で区切られたチャンクに分割することのようです。bash では、ワイルドカード展開をオフにしてから (デフォルトから変更されていないと仮定して) 引用符なしの展開を使用することrun2でこれを実現できます。IFS

run2 () ( set -f; run $1; )

(echo "$(somecommand)"は、本質的にはサブシェルで実行するのと同じでsomecommandあり、コマンドの出力に split+glob を適用するのではなく、これが意図されていることのようですecho $(somecommand)。そのため、冗長な echo-command-substitution を削除しました。)

zsh では、=パラメータ置換で文字を使用して、値に対してワールド分割(グロブなし)を実行できます。

run2 () { run $=1; }

zsh の構文は、通常の sh の構文と互換性がありません。zsh から sh スクリプトをソースする場合は、次のemulateビルドを使用できます。

emulate sh -c '. myscript.sh'

ksh の (一部の) 機能をエミュレートするために使用しますemulate ksh。これは bash のすべての機能をエミュレートするわけではありませんが、配列を使用できるようになります。

¹より一般的には、 の値に基づきますIFS。²
これがオフになっていない限りset -f

答え2

引用と引用の驚きの世界へようこそ。

主な問題は、zshデフォルトでは IFS 文字で分割されないことです。
その点では、他のすべてのシェルとは異なります。

引用符によってシェルの動作がどのように変化するかをテストするには、引用符で囲まれたコードと引用符で囲まれていないコードの両方をテストするコードが必要です。

コード(いくつかの変数を追加):

program() { printf '%02d-1: %6s   2: %6s    3: %6s' "$i" "$1" "$2" "$3"; }
runa()  { program  "$@" ; }
run1()  { echo "`runa  $1 $2 $3 `"; }
run1    'a b' c

詳細を説明させてください。

sh居住地を指すローカル リンクを作成すると仮定しますzsh

ln -s "/usr/bin/zsh" ./sh

また、次のスクリプトをファイルにコピーするとしますso

引用符で囲まれた変数と引用符で囲まれていない変数を使用して各関数を繰り返すスクリプト:

program() { printf '%02d-1: %6s   2: %6s    3: %6s' "$i" "$1" "$2" "$3"; }
runa() { program "$@"; }
runb() { program  $@ ; }

run1() { echo "`runa  "$1" "$2" "$3"`"; }
run2() { echo "`runb  "$1" "$2" "$3"`"; }
run3() { echo "`runa  $1 $2 $3`"; }
run4() { echo "`runb  $1 $2 $3`"; }

for i in `seq 4`; do
    run"$i" 'a b' c
done

次に、実行すると、次のように出力されます。

# Any shell (except zsh) results.
01-1:    a b   2:      c    3:
02-1:      a   2:      b    3:      c
03-1:      a   2:      b    3:      c
04-1:      a   2:      b    3:      c

すべてが引用されている最初の実行 (run1) のみが'a b'結合されたままになります。

ただし、zsh は常に all が引用符で囲まれているかのように動作します。

# ZSH results.
01-1:    a b   2:      c    3:
02-1:    a b   2:      c    3:
03-1:    a b   2:      c    3:
04-1:    a b   2:      c    3:

zsh のエミュレーション。

またはzshとして呼び出された場合、古いシェルをエミュレートするはずです。 しかし、実際にはこれは常に当てはまるとは限りません。shksh

$ ./sh ./so   # emulated sh
01-1:    a b   2:      c    3:
02-1:    a b   2:      c    3:
03-1:      a   2:      b    3:      c
04-1:      a   2:      b    3:      c

2 行目は他のシェルの 2 行目とは異なります。

良いアイデアですこの質問の答えを読む

関連情報