と${parameter%word}
一緒に拡張機能を使用する方法を理解しようとしています。私はGhostscriptを使ってpdfを結合するスクリプトを作成しようとしましたが、パラメータ拡張で奇妙な動作が発生し、今はなぜこのような動作が発生するのか疑問に思います。$@
$*
デフォルトでは、各パラメータの末尾から「.pdf」を削除し、ランダムな文字列で連結し(テストには「-」を使用します)、最後に「.pdf」を追加します。結果。たとえば、予想される動作はtest.sh a.pdf b.pdf c.pdf
->ですa-b-c.pdf
。私が実行しているテストスクリプトは次のとおりです。
IFS='-'
echo ${*%.pdf}.pdf
echo "${*%.pdf}.pdf"
a=${*%.pdf}.pdf
b="${*%.pdf}.pdf"
echo $a
echo $b
私がしたら、bash test.sh a.pdf b.pdf c.pdf
次のようになります。
a b c.pdf
a-b-c.pdf
a b c.pdf
a b c.pdf
私がしたら、zsh test.sh a.pdf b.pdf c.pdf
次のようになります。
a b c.pdf
a.pdf-b.pdf-c.pdf
a-b-c.pdf
a.pdf-b.pdf-c.pdf
私はzshとbashが異なることを知っているので、なぜ他の結果が出るのか心配しません。ただし、各場合に文字列を作成する4つの方法のうちの1つだけが期待どおりに機能します(2番目はbash、3番目はzsh)。
文字列を構築しようとしているように見える試みがなぜそれほど異なる結果を生むのでしょうか?どんな洞察力でも感謝します。ありがとうございます!
答え1
動作方法${*%word}
などはシェルによって異なります。POSIX指定された結果はありません。 2つの主な可能な動作があります。変換(プレフィックスまたはサフィックスの削除)は、各単語に適用することも、単語連結結果に適用することもできます。配列をサポートするシェルでは、各単語に変換を適用するのは当然です。これがbashとksh93が行うことです。配列をサポートしていないシェルでは、最初に単語を連結するのは当然です(ash / dashがすることです)。たとえば、
# No arrays: $* joined = 'abc abc'; strip off b* → 'a'
$ dash -c 'echo ${*%%b*}' _ abc abc
a
# Arrays: $* = ('abc' 'abc'); strip off b* from each element → ('a' 'a'); then join
$ bash -c 'echo ${*%%b*}' _ abc abc
a a
最初の文字IFS
は、形成された単語を連結するために使用されます$*
。パターンがその文字と一致する場合にのみ、削除されたコンテンツに影響します。たとえば、
# No arrays: $* joined = 'abc-def-ghi'; strip off -* → 'abc'
$ dash -c 'IFS=-; echo "${*%%-*}"' _ abc-def ghi
abc
# Arrays: $* = ('abc-def' 'ghi'); strip off -* from each element → ('abc' 'ghi'); then join
$ bash -c 'IFS=-; echo "${*%%-*}"' _ abc-def ghi
abc-ghi
置換が単語コンテキストにあるとき、この最後まで拡張されます。単語のコンテキストには二重引用符と割り当てが含まれます。いつ二重引用符が必要ですか?そしてシェル変数の拡張とglobと分割の影響詳細については。これは説明しますecho "${*%.pdf}.pdf"
:の最初の文字はIFS
接続に使用され、後続の分割がないため、bashの出力はですa-b-c.pdf
。どちらの値もa
同じb
ですa-b-c.pdf
。
最初の例のように、代替項目がリストのコンテキストにある場合(つまり、引用符が付けられていない場合)、結果はトークン化されます(グロービング)。これは、を基準にIFS
するので、、a-b-c.pdf
で区切られます。このコマンドは、間にスペースがある3つの単語を印刷します。あなたの例では、andでも同じことが起こります。 orの値は文字に分割されます。a
b
c.pdf
echo
echo $a
echo $b
a
b
IFS
Zshは取り扱って@
異なり*
ます。パラメータ名で*
二重引用符内に文字列スタイルの動作(連結後の変換)を適用し、それ以外の場合は配列スタイルの動作(各要素の変換)を適用します。一方、パラメータは@
常に配列として扱われます。したがって:
$ zsh -c 'echo "${*%.pdf}"' _ a.pdf b.pdf c.pdf
a.pdf b.pdf c
$ zsh -c 'echo ${*%.pdf}' _ a.pdf b.pdf c.pdf
a b c
$ zsh -c 'echo "${@%.pdf}"' _ a.pdf b.pdf c.pdf
a b c
$ zsh -c 'echo ${@%.pdf}' _ a.pdf b.pdf c.pdf
a b c
他のシェルとは異なり、文字列の割り当てにより文字列が処理されません$*
。二重引用符が重要です。これはa=${*%.pdf}; echo $a
、likeが同じように機能しているのとecho ${*%.pdf}
同じように機能しない理由を説明しますa="${*%.pdf}"; echo $a
。
の場合、二重引用符または文字列の割り当てにより、IFS=-
接続時にダッシュが使用されます。*
これは、単語のコンテキストにある限り発生します。
# ('a.pdf' 'b.pdf' 'c.pdf); strip each element → ('a' 'b' 'c'); print list
$ zsh -c 'IFS=-; echo ${*%.pdf}' _ a.pdf b.pdf c.pdf
a b c
# join → 'a.pdf-b.pdf-c.pdf'; strip the single word and print it
$ zsh -c 'IFS=-; echo "${*%.pdf}"' _ a.pdf b.pdf c.pdf
a.pdf-b.pdf-c
# ('a.pdf' 'b.pdf' 'c.pdf); strip each element → ('a' 'b' 'c'); `$*` in word context so join → 'a-b-c'; print word
$ zsh -c 'IFS=-; a=${*%.pdf}; echo "$a"' _ a.pdf b.pdf c.pdf
a-b-c
# join → 'a.pdf-b.pdf-c.pdf'; strip the single word; print the word
$ zsh -c 'IFS=-; a="${*%.pdf}"; echo "$a"' _ a.pdf b.pdf c.pdf
a.pdf-b.pdf-c
をほとんど使用しないでください$*
。位置引数を連結するのにのみ役立ち、接続によって生成された文字と引数にすでに存在する文字をIFS
区別することはできません。ほぼ常に正しい形式です。単語の拡張を防ぐには二重引用符が必要です(zshでも省略すると、zshへの影響ははるかに少なくなります)。IFS
IFS
"$@"
スクリプトを理解しやすくするには、一度に 1 ステップずつ実行します。つまり、各部分からサフィックスを削除してから部分を接続します。配列変数を使用して中間結果を保存します。
parts=("${@%.pdf}") # using @ because we want to have array behavior
IFS=-
joined="${parts[*]}" # using * and not @ for joining
echo "$joined.pdf"
このコードスニペットはbashとzshで同じように動作します。
答え2
これは何が起こるか説明します:
#!/bin/bash
IFS='-'
var='a-b-c.pdf'
echo $var
echo "$var"
echo ${*%.pdf}.pdf
目的の文字列を生成しましたが、引用符が欠落しているため、単語の分割が-
。
またはこれ:
[[ ${*%.pdf}.pdf =~ ' ' ]]
+ [[ a-b-c.pdf =~ ]]
echo $?
+ echo 1
1
これには[[ ]]
噴射がなく、set -vx
/はbash -vx
拡張にスペースが含まれていないことを示します。