チルダを変数の一部として展開するにはどうすればよいですか?


12

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ます。


ZSHではこれですx='~'; print -l ${x} ${~x}bashしばらくマニュアルを掘り下げてあきらめました。
thrig

@thrig:これはバシズムではなく、この動作はPOSIXです。
WhiteWinterWolf 2017年

非常に密接に関連して(ないだまされやすい人の場合):unix.stackexchange.com/questions/151850/...
Kusalananda

@Kusalananda:ここでの理由が多少異なるため、これがだまされているとは思えません。OPは、エコー時に$ xを引用符で囲みませんでした。
WhiteWinterWolf 2017年

入力ファイルにチルダを入れません。問題が解決しました。
chepner 2017年

回答:


11

POSIX標準以下の順序で行われる課しワード拡張(鉱山で強調):

  1. チルダ拡張チルダ拡張を参照)、パラメータ拡張パラメータ拡張を参照)、コマンド置換(コマンド置換を参照)、および算術拡張(算術拡張を参照)が最初から最後まで実行されます。トークン認識の項目5を参照してください。

  2. IFSがnullでない限り、フィールド分割(「フィールド分割」を参照)は、手順1で生成されたフィールドの部分に対して実行されます。

  3. set -fが有効になっていない限り、パス名展開(パス名展開を参照)が実行されます。

  4. 引用の削除(引用の削除を参照)は常に最後に実行する必要があります。

ここで私たちが興味を持つ唯一の点は最初の点です。ご覧のとおり、チルダ展開はパラメータ展開の前に処理されます。

  1. シェルはでチルド拡張を試みますが、チルドecho $xが見つからないため、続行します。
  2. シェルは、上のパラメーターの拡張を試みecho $x$x発見し、拡大し、コマンドラインとなりますecho ~/someDirectory
  3. 処理は続行されますが、チルダ拡張はすでに処理されているため、~文字はそのまま残ります。

を割り当てるときに引用符を使用$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、展開せずにチルダを変数に影響を与えて、echoコマンドで展開できるようにする必要がある$x場合は、変数の2つの展開を強制的に実行するために、2回続行する必要があります。

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#"~/"}"、次のように見えます。

  1. ${HOME}そして${x#"~/"}拡張されなければなりません。
  2. ${HOME}$HOME変数の内容に展開されます。
  3. ${x#"~/"}ネストされた展開をトリガーします:"~/"解析されますが、引用符で囲まれているため、リテラル1として扱われます。ここでは単一引用符を使用しても同じ結果になります。
  4. ${x#"~/"}式自体が拡張され、その結果、プレフィックス~/がの値から削除されます$x
  5. 上記の結果が連結されました:の展開${HOME}、リテラル/、展開${x#"~/"}
  6. 最終結果は二重引用符で囲まれ、機能的に単語の分割を防ぎます。私は(参照、これらの二重引用符が技術的に必要とされていないので、機能的に、ここで言うここそこに例えば)が、個人的なスタイルとしてすぐに割り当てよう超えて何につれてa=$b、私は通常、それが明確に二重引用符を追加見つけるに。

ちなみに、case構文を詳しく見てみると、上で説明"~/"*したのと同じ概念に依存する構成がわかりますx=~/'someDirectory'(ここでも、二重引用符と単純引用符は同じ意味で使用できます)。

これらのものが一目では見えないように見えるかもしれませんが、心配しないでください(たぶん2番目以降の光景でも!)。私の意見では、パラメーターの展開は、サブシェルでは、シェル言語でプログラミングするときに把握する最も複雑な概念の1つです。

私は一部の人が激しく反対することを知っていますが、シェルプログラミングについてさらに詳しく知りたい場合は、「高度なBashスクリプトガイド」を読むことをお勧めします。Bashスクリプトについて説明しているため、多くの拡張機能と標準機能を備えています。ホイッスルはPOSIXシェルスクリプティングと比較されましたが、実用的な例がたくさん書かれていることがわかりました。これを管理すれば、必要なときにPOSIX機能に制限するのは簡単です。POSIXレルムに直接入力することは、初心者にとって不要な急な学習曲線であると個人的には思っています(私のPOSIXチルドを@ m0dularの正規表現のような正規表現で置き換えると比較してください)私が何を言っているのかを理解するのと同じです;)!)


1:これにより、ダッシュが正しく実装されていないDashのバグが見つかります(を使用して検証可能x='~/foo'; echo "${x#~/}")。パラメータの展開は、ユーザーとシェル開発者自身の両方にとって複雑なフィールドです。


bashシェルはどのように行を解析していx="${HOME}/${x#"~/"}"ますか?:それは3つの文字列の連結のように見える"${HOME}/${x#"~/"}"。二重引用符の内側のペアが${ }ブロック内にある場合、シェルはネストされた二重引用符を許可しますか?
Andrew

@Andrew:うまくいけば、あなたのコメントに対処するための追加情報で私の答えを完成させました。
WhiteWinterWolf 2017年

ありがとう、これは素晴らしい答えです。私はそれを読んでたくさんのことを学びました。私はそれを複数回
Andrew

@WhiteWinterWolf:シェルは、結果が何であれ、ネストされた引用符を表示しません。
avp 2018年

6

考えられる答えの1つ:

eval echo "$x"

ファイルから入力を読み取っているので、これは行いません。

次のように、〜を検索して$ HOMEの値で置き換えることができます。

x='~/.config'
x="${x//\~/$HOME}"
echo "$x"

くれます:

/home/adrian/.config

${parameter/pattern/string}拡張はBash拡張であり、他のシェルでは使用できない場合があることに注意してください。
WhiteWinterWolf 2017年

そうだね。OPは彼がBashを使用していると述べたので、それは適切な答えだと思います。
m0dular 2017年

私が同意するのは、Bashに固執している限り、それを十分に活用しない理由です(誰もがどこでもポータビリティを必要とするわけではありません)。 )影響を受けるユーザーは驚くことではありません。
WhiteWinterWolf 2017年

私のPOSIX case構造と比較したBashの単一行の正規表現のようなステートメントは、Bashスクリプトの方が特にユーザーフレンドリーであることがよくわかると思います初心者向け。
WhiteWinterWolf 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.