web-dev-qa-db-ja.com

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

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を使用して、同様の結果が得られます。

11
Andrew

POSIX標準 は、Wordの拡張を次の順序で実行するように強制します(強調は私のものです)。

  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:~$

また、beforeパラメータ展開が発生する可能性がある限り、echoコマンドラインで展開を発生させることもできます。

user@Host:~$ x='someDirectory'
+ x=someDirectory
user@Host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@Host:~$

なんらかの理由で、展開せずに$x変数のチルダに影響を与え、echoコマンドでそれを展開できるようにする必要がある場合は、2回実行して、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標準によって課されるように、引用の削除は最後に行われ、パラメーターの展開は非常に早く行われます。したがって、${#"~"}は、外側の引用符の評価のはるか前に評価および展開されます。 パラメータ展開 ルールで定義されているように:

Wordの値が必要な場合(パラメーターの状態に基づいて、以下で説明します)ごとに、Wordはチルド展開、パラメーター展開、コマンド置換、および算術展開の対象になります。

したがって、チルダ展開を回避するには、#演算子の右側を適切に引用符で囲むか、エスケープする必要があります。

つまり、別の言い方をすると、シェルインタープリターがx="${HOME}/${x#"~/"}"を見ると、次のように見えます。

  1. ${HOME}および${x#"~/"}は展開する必要があります。
  2. ${HOME}は、$HOME変数の内容に展開されます。
  3. ${x#"~/"}はネストされた展開をトリガーします:"~/"は解析されますが、引用符で囲まれているため、リテラルとして扱われます1。ここでは単一引用符を使用しても同じ結果になります。
  4. ${x#"~/"}式自体が拡張され、接頭辞~/$xの値から削除されます。
  5. 上記の結果が連結されました:${HOME}の展開、リテラル/、展開${x#"~/"}
  6. 最終結果は二重引用符で囲まれ、Wordの分割を機能的に防ぎます。これらの二重引用符は技術的に必要ではないため(たとえば here および there を参照)、ここで機能的に言​​いますが、割り当てがa=$b通常、二重引用符を追加するとわかりやすくなります。

ちなみに、case構文を詳しく見ると、上記で説明した"~/"*と同じ概念に依存するx=~/'someDirectory'構文が表示されます(ここでも、doubleと単純な引用符は交換可能に使用できます)。

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

私は一部の人が激しく反対することを知っていますが、シェルプログラミングについてより深く学びたい場合は、 Advanced Bash Scripting Guide を読むことをお勧めします。 POSIXシェルスクリプティングと比較して、拡張機能と口笛の数が多かったのですが、多くの実用的な例が書かれていることがわかりました。これを管理すれば、必要なときにPOSIX機能に制限するのは簡単です。POSIXレルムに直接入力することは、初心者にとって不要な急な学習曲線であると個人的に考えています(私のPOSIXチルドを@ m0dularの正規表現のような正規表現で置き換えると比較してください)私が何を意味するのかを理解するのと同じです;)!).


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

11
WhiteWinterWolf

考えられる答えの1つ:

eval echo "$x"

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

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

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

くれます:

/home/adrian/.config
6
m0dular