シェルコマンドをsed式に埋め込む方法は?


16

次の形式のテキストファイルがあります。

keyword value
keyword value
...

キーワードは単一の単語であり、値は行末までその他すべてです。値(キーワードではなく)がシェル拡張されるように、シェルスクリプトからファイルを読み取りたいです。

sedを使用すると、キーワードと値の部分を簡単に一致させることができます

input='
keyword value value
keyword "value  value"
keyword `uname`
'

echo "$input"|sed -e 's/^\([^[:space:]]*\)[[:space:]]\(.*\)$/k=<\1> v=<\2>/'

生産する

k=<keyword> v=<value value>
k=<keyword> v=<"value  value">
k=<keyword> v=<`uname`>

しかし、問題は、シェルコマンドをsed式の置換部分にどのように埋め込むことができるかです。この場合、私は交換が欲しい\1 `echo \2`です。


ええと...答えとしてそれを提供することは確かではありませんが、sedで引用されたDOUBLEを使用すると、式の中でshell $(command)または$ variablesを使用できます。
St0rM

回答:


18

標準のsedはシェルを呼び出すことができません(GNU sedには拡張機能がありますが、非組み込みLinuxのみに関心がある場合)。そのため、sedの外部で処理を行う必要があります。いくつかの解決策があります。すべて慎重に引用する必要があります。

値をどのように展開するかは明確ではありません。たとえば、行が

foo hello; echo $(true)  3

出力は次のうちどれですか?

k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo 3>
k=<foo> value=<foo hello
  3>

以下にいくつかの可能性について説明します。

純粋な殻

シェルに入力を1行ずつ読み取らせて処理させることができます。これは最も単純なソリューションであり、短いファイルでも最速です。これは、要件「echo \2」に最も近いものです。

while read -r keyword value; do
  echo "k=<$keyword> v=<$(eval echo "$value")>"
done

read -r keyword value$keywordの最初の空白で区切られた単語に設定し、行$valueの残りから末尾の空白を引いた値に設定します。

変数参照を展開したいが、コマンド置換の外でコマンドを実行したくない場合は$valueヒアドキュメント内に入れてください。これがあなたが本当に探していたものだと思う。

while read -r keyword value; do
  echo "k=<$keyword> v=<$(cat <<EOF
$value
EOF
)>"
done

シェルにパイプされたsed

入力をシェルスクリプトに変換して評価できます。Sedはタスク次第ですが、それほど簡単ではありません。「echo \2」の要件に沿って(キーワード内の単一引用符をエスケープする必要があることに注意してください):

sed  -e 's/^ *//' -e 'h' \
     -e 's/[^ ]*  *//' -e 'x' \
     -e 's/ .*//' -e "s/'/'\\\\''/g" -e "s/^/echo 'k=</" \
     -e 'G' -e "s/\n/>' v=\\</" -e 's/$/\\>/' | sh

ヒアドキュメントを使用して、キーワードをエスケープする必要があります(ただし、異なります)。

{
  echo 'cat <<EOF'
  sed -e 's/^ */k=</' -e 'h' \
      -e 's/[^ ]*  *//' -e 'x' -e 's/ .*//' -e 's/[\$`]/\\&/g' \
      -e 'G' -e "s/\n/> v=</" -e 's/$/>/'
  echo 'EOF'
 } | sh

これは、大量のデータがある場合の最速の方法です。各行に対して個別のプロセスを開始しません。

awk

awkのsedで使用したのと同じテクニック。結果のプログラムはかなり読みやすくなります。「echo \2」で行く:

awk '
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\047/, "\047\\\047\047", $1);
      print "echo \047k=<" kw ">\047 v=\\<" $0 "\\>";
  }' | sh

ヒアドキュメントの使用:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh

素晴らしい答え。入力ファイルは確かに小さく、パフォーマンスは問題にならないので、純粋なシェルソリューションを使用します。また、クリーンで読みやすいです。
アーネストAC

ちょっとしたハックですが、きちんとしています。たとえば、sedを使用してxxdを呼び出し、長い16進文字列をデコードします。。。猫FtH.ch13 | sed -r 's /(.* text。*:[)([0-9a-fA-F] *)] / \ 1 $(echo \ 2 | xxd -r -p)] /; s / ^( 。*)$ / echo "\ 1" / g '| bash> FtHtext.ch13 FtH.ch13には「foo bar hex text test:[666f6f0a62617200]」
gaoithe

14

GNU sedを使用すると、次のコマンドを使用できます。

sed -nr 's/([^ ]+) (.*)/echo "\1" \2\n/ep' input

どの出力:

keyword value value
keyword value  value
keyword Linux

入力データで。

説明:

sedコマンドは、-nオプションを使用して通常の出力を抑制します。-rパターン内の特殊文字のエスケープを節約する拡張正規表現を使用するために渡されますが、必須ではありません。

このsコマンドは、入力行をコマンドに転送するために使用されます。

echo "\1" \2

キーワードは値を引用符で囲まない。eGNU固有のオプションをsコマンドに渡します。このコマンドは、sedに置換の結果をシェルコマンドとして実行し、その結果をパターンバッファーに読み込みます(複数行でも)。オプションpafter(!)eを使用するとsed、コマンドの実行後にパターンバッファーが印刷されます。


-npオプションの両方がなくても実行できますsed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input。しかし、これに感謝します!eオプションについて知りませんでした。
カウシャルモディ

@KaushalModiああ、そうだね!eオプション(GNUによって導入)に関しては、フェンスの上に座っています。まだsed?:)
hek2mgl

まあ、それは私のために働いた。RHELディストリビューションでは、デフォルトではGNU sed(GNU sedバージョン4.2.1)です。
カウシャルモディ

4

このアプローチを試すことができます:

input='
keyword value value
keyword "value  value"
keyword `uname`
'

process() {
  k=$1; shift; v="$*"
  printf '%s\n' "k=<$k> v=<$v>"
}

eval "$(printf '%s\n' "$input" | sed -n 's/./process &/p')"

(あなたの意図が正しい場合)。つまり、空ではない各行の先頭に「プロセス」を挿入して、次のようなスクリプトにします。

process keyword value value
process keyword "value  value"
process keyword `uname`

評価される(eval)ここで、processは期待されるメッセージを出力する関数です。


1

非sedソリューションが受け入れられる場合、このPERLスニペットは次のことを行います。

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'

1
感謝しますが、できれば標準のUNIXコマンドとbourneシェルにそれを維持する場合は、別のスクリプト言語の使用を避けたいと思います
アーネストAC

0

キスショートピュアシードのみ

これをするつもりです

echo "ls_me" | sed -e "s/\(ls\)_me/\1/e" -e "s/to be/continued/g;"

そしてそれは働いています。


どのように機能するのか説明していただけますか?
エリッシュ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.