bashプロンプトを開き、次のように入力します。
$ set -o xtrace
$ x='~/someDirectory'
+ x='~/someDirectory'
$ echo $x
+ echo '~/someDirectory'
~/someDirectory
上記の5行目が消えたらと思います+ echo /home/myUsername/someDirectory
。これを行う方法はありますか?元の Bash スクリプトでは、変数 x は実際には次のループを介して入力ファイルのデータで埋められます。
while IFS= read line
do
params=($line)
echo ${params[0]}
done <"./someInputFile.txt"
それでもecho '~/someDirectory'
代わりにecho /home/myUsername/someDirectory
。
ベストアンサー1
これPOSIX規格単語拡張が次の順序で行われるように強制します(強調):
チルダ拡張(チルダ拡張を参照)、パラメータ拡張(パラメータ拡張を参照)、コマンド置換(コマンド置換参照)、および算術拡張(算術拡張を参照)は、最初から最後まで実行する必要があります。トークン認識のトピック5を参照してください。
IFSが空でない場合は、手順1で作成したフィールド部分に対してフィールド分割を実行する必要があります(フィールド分割を参照)。
set -fが適用されない限り、パス名拡張を実行する必要があります(パス名拡張を参照)。
見積もりの削除(見積もりの削除を参照)は、常に最後に実行する必要があります。
私たちが興味を持っている唯一のことは最初のものです。ご覧のとおり、チルダ拡張は引数拡張の前に処理されます。
- シェルはのチルダを拡張しようとします
echo $x
が、チルダが見つからずに続行します。 - シェルはパラメータ拡張を試み、見つけて拡張し、
echo $x
コマンド$x
ラインはecho ~/someDirectory
。 - 処理が継続され、チルダ拡張が処理され、
~
文字はそのまま残ります。
を割り当てるときに引用符を使用すると、$x
チルダが拡張されず、通常の文字として扱われないように明示的に要求します。しばしば見落とされるのは、シェルコマンドで文字列全体を引用する必要がないため、変数の割り当て中に拡張できることです。
user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
また、拡張がecho
発生する限り、コマンドラインで拡張を実行することもできます。今後パラメータ拡張:
user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
何らかの理由で$x
拡張なしで変数のチルダに影響を与え、コマンドで拡張できる必要がある場合は、変数を2回拡張するように強制的にecho
2回実行する必要があります。$x
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
ただし、これらの構文が使用される状況によっては望ましくない副作用がある可能性があることに注意してください。経験上eval
、他の方法がある場合は、これを必要とする項目を使用しないことをお勧めします。
他の種類の拡張ではなくチルダの問題を具体的に解決したい場合、この構造はより安全で移植性に優れています。
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
> x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
この構造は先行ディレクトリがあるかどうかを明示的に確認し、~
見つかった場合はそれをユーザーのホームディレクトリに置き換えます。
あなたの意見によると、x="${HOME}/${x#"~/"}"
これはシェルでプログラムされていない人にとっては本当に驚くべきことですが、実際には上記で引用したのと同じPOSIXルールに関連しています。
POSIX規格によると、引用符の削除は最後に行われ、パラメータの拡張は早く起こります。したがって、${#"~"}
外部提案を評価するずっと前に評価され拡張されます。順番に定義されたとおりパラメータ拡張ルール:
単語の値が必要な場合(後述の引数の状態に応じて)、単語はチルダ拡張、パラメータ拡張、コマンド置換、および算術拡張を受けなければなりません。
#
したがって、チルダの拡張を防ぐには、演算子の右側の部分を正しく引用またはエスケープする必要があります。
つまり、シェルインタプリタがそれを見るときは、次のことをx="${HOME}/${x#"~/"}"
確認します。
${HOME}
そして${x#"~/"}
拡張する必要があります。${HOME}
変数の内容に展開されます$HOME
。${x#"~/"}
入れ子になった拡張をトリガーします。"~/"
解析されたが引用されている場合は、リテラル1として扱われます。ここで一重引用符を使用すると、同じ結果が得られます。${x#"~/"}
式自体が拡張され、~/
値からプレフィックスが削除されます$x
。- 上記の結果は、拡張
${HOME}
、リテラル/
、拡張につながります${x#"~/"}
。 - 最終結果は二重引用符で囲まれ、単語の分割を機能的に防止します。ここでは、これらの二重引用符は技術的に必要ないので機能的であると言います(参照ここそしてそこ例)しかし、個人的に
a=$b
課題が範囲外であれば、二重引用符を追加することがより明確であることがわかりました。
しかし、構文をさらに詳しく見ると、その構成は上記のものと同じ概念に依存しているcase
ことがわかります(ここでも二重引用符と二重引用符を入れ替えて使用できます)。"~/"*
x=~/'someDirectory'
このような内容が一見で曖昧に見えても心配しないでください(おそらく2番目またはそれ以降に見えるかもしれません!)。私の考えでは、パラメータ拡張とサブシェルは、シェル言語でプログラムするときにマスターする必要がある最も複雑な概念の1つです。
一部の人々は大幅に同意しないかもしれないことを知っています。しかし、シェルプログラミングについてもっと知りたい場合は、次の内容をお読みください。高度なバッシュスクリプトガイド: Bash スクリプトを教えるため、POSIX シェルスクリプトに比べて拡張と追加機能が多いですが、実用的な例も多く、よく書かれているようです。そうすれば、必要なときにPOSIX機能に制限するのが簡単で、個人的にPOSIX領域に直接飛び込むことは、初心者にとって不必要に急な学習曲線だと思います(私のPOSIXチルダ置換を変換(@m0dularの正規表現と比較)。 Bashに似ています)どういう意味かわかるのと同じです;)! )。
1:これにより、チルダ拡張が正しく実装されていないDashのバグが見つかりました(x='~/foo'; echo "${x#~/}"
.パラメータ拡張は、ユーザーとシェル開発者の両方にとって複雑な領域です!