1. 概要

sed は、Linuxコマンドラインの強力なテキスト処理ツールです。 コンパクトなsedワンライナーを使用してテキスト置換を行うことがよくあります。 彼らはかなり便利です。

ただし、シェル変数を使用してsed置換を実行する場合、注意すべきいくつかの落とし穴があります

このチュートリアルでは、シェル変数で sed 置換を使用して発生する一般的な間違いを詳しく見て、いくつかの解決策を提供します。

2. 問題の例

これらのよくある間違いをより簡単に再現し、それらを修正する方法を議論するために、問題の例を作成しましょう。

ファイルtest.txtがあるとします。

$ cat test.txt
CURRENT_TIME = # fill the current date and time
JAVA_HOME = # fill the JAVA_HOME path

上記のファイルに、現在の時刻と現在のシステムのJAVA_HOMEパスを入力するシェルスクリプトを記述します。

タスクは簡単に見えます。 ただし、いくつかの潜在的な問題があります。

GNUsedを使用して一緒にスクリプトを書いてみましょう。

3. どの引用符を使用する必要がありますか?

問題に応じて、現在時刻とJAVA_HOMEパスの2つの置換を実行する必要があります。

まず、現在の時刻を適切な場所に入力しましょう。 date コマンドを使用して、現在の時刻を取得できます。

$ cat solution.sh 
#!/bin/sh
MY_DATE=$(date)
sed -i -r 's/^(CURRENT_TIME =).*/\1 $MY_DATE/' test.txt

上記のスクリプトを理解するのは難しいことではありません。 さっそく見ていきましょう。

まず、コマンド置換から現在の日付と時刻を取得し、それを変数MY_DATEに保存します。

日付を取得したら、sed置換を使用してファイルに入力します。 GNU sedコマンドの-iオプションを使用して、インプレース編集を実行しました。

スクリプトを実行して、期待どおりに機能するかどうかを確認しましょう。

$ ./solution.sh
$ cat test.txt 
CURRENT_TIME = $MY_DATE
JAVA_HOME = # fill the JAVA_HOME path

上記の出力が示すように、「 CURRENT_TIME=」の行が置き換えられています。 ただし、現在の日付と時刻の代わりに、文字通りの「 $MY_DATE」が入力されます。

これは、sedコマンドで一重引用符を使用したために発生しました。 シェル変数は一重引用符で囲まれません

したがって、簡単な修正は sedコマンドで二重引用符を使用してシェル変数の拡張を許可することです

$ cat solution.sh
#!/bin/sh
MY_DATE=$(date)
sed -i -r "s/^(CURRENT_TIME =).*/\1 $MY_DATE/" test.txt

それでは、solution.shスクリプトをもう一度テストしてみましょう。

$ ./solution.sh 
$ cat test.txt 
CURRENT_TIME = Wed Jan 27 10:02:05 PM CET 2021
JAVA_HOME = # fill the JAVA_HOME path

良い! 日付と時刻を適切な場所に入力しました。

次に、ファイルにJAVA_HOMEパスを入力します。

4. どの区切り文字を使用する必要がありますか?

現在の時間の置換が機能しているので、JAVA_HOMEの部分は多かれ少なかれコピーアンドペーストの仕事であると考えるかもしれません。

本当に簡単ですか? solution.shスクリプトにsedコマンドをもう1つ追加しましょう。

$ cat solution.sh
...
sed -i -r "s/^(CURRENT_TIME =).*/\1 $MY_DATE/" test.txt
sed -i -r "s/^(JAVA_HOME =).*/\1 $JAVA_HOME/" test.txt

スクリプトをテストする時が来ました:

$ ./solution.sh 
sed: -e expression #1, char 24: unknown option to `s'

おっとっと! 新しく追加されたsedコマンドは機能しません。 再確認すると、他の動作している sed コマンドと非常によく似ており、変数のみが異なります。

どうしたの?

4.1. 変数に含まれていない区切り文字の選択

何が起こったのかを理解するために、まず環境変数 $JAVA_HOMEの内容を確認しましょう。

$ echo $JAVA_HOME 
/usr/lib/jvm/default

シェル変数は二重引用符で囲まれて展開されることを学びました。 したがって、変数の展開後、2番目のsedコマンドは次のようになります。

sed -i -r "s/^(JAVA_HOME =).*/\1 /usr/lib/jvm/default/" test.txt

さて、上記の sed コマンドは明らかに機能しません。これは、変数の値のスラッシュ(/)が’s’コマンド s / pattern / replace / )。

幸い、「s」コマンドの区切り文字として他の文字を選択できます

2番目のsedコマンドを少し変更して、 s コマンドの区切り文字として「#」を使用してみましょう。

sed -i -r "s#^(JAVA_HOME =).*#\1 $JAVA_HOME#" test.txt

それでは、スクリプトをもう一度テストしてみましょう。

$ ./solution.sh
$ cat test.txt 
CURRENT_TIME = Wed Jan 27 10:36:57 PM CET 2021
JAVA_HOME = /usr/lib/jvm/default

すごい! 問題は解決しました—それともそうですか?

4.2. より良い解決策

実際、ほとんどの場合、solution.shが機能します。 ただし、ほとんどの* nixファイルシステムでは、「#」はファイル名有効な文字であることに注意してください。

つまり、ある日、JAVA_HOME/opt /#jvm#に設定されているシステムでスクリプトを実行すると、スクリプトは再び失敗します。

すべてのケースでスクリプトを機能させるために、次のことができます。

  • まず、sedのsコマンドの区切り文字を選択します。 読みやすさを向上させるために、区切り文字として「#」を使用するとします。
  • 次に、変数のコンテンツ内のすべての区切り文字をエスケープします
  • 最後に、sedコマンドでエスケープされたコンテンツをアセンブルします

Bash置換を使用して区切り文字をエスケープできます。 たとえば、変数 $ VAR 内のすべての「#」文字をエスケープできます。

$ VAR="foo#bar#blah"
$ echo "${VAR//#/\\#}"
foo\#bar\#blah

それをスクリプトに適用してみましょう。

$ cat solution.sh 
#!/bin/sh
MY_DATE=$(date)
sed -i -r "s/^(CURRENT_TIME =).*/\1 $MY_DATE/" test.txt
sed -i -r "s#^(JAVA_HOME =).*#\1 ${JAVA_HOME//#/\\#}#" test.txt

次に、シミュレートされた JAVA_HOME 変数を使用してスクリプトを実行し、期待どおりに機能するかどうかを確認します。

$ JAVA_HOME=/opt/#/:/@/-/_/$/jvm ./solution.sh
$ cat test.txt
CURRENT_TIME = Thu Jan 28 11:23:07 AM CET 2021
JAVA_HOME = /opt/#/:/@/-/_/$/jvm

出力が示すように、スクリプトは、JAVA_HOME変数に多くの特殊文字が含まれている場合でも機能します。

5. 結論

この記事では、シェル変数を使用してsed置換を作成するときに発生する可能性のあるいくつかの一般的な間違いについて説明しました。

また、例を通じてこれらの問題を解決する方法についても説明しました。