web-dev-qa-db-ja.com

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

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

keyword value
keyword value
...

ここで、keywordは単一のWordであり、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`

17
Ernest A C

標準の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行の最初の空白で区切られたWordに、そして$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

Sedで使用したのと同じテクニックがawkで動作します。結果のプログラムはかなり読みやすくなります。 「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

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

キーワードgetは値を引用していません。オプションe-これはGNU固有です-sコマンドに渡します。これは、置換の結果をシェルコマンドとして実行するようにsedに指示します。結果をパターンバッファに読み込む(複数行でも可)オプションp after(!)eを使用すると、コマンドの実行後にパターンバッファがsedに出力されます。

14
hek2mgl

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

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は、期待されるメッセージを出力する関数です。

4

Sed以外のソリューションが受け入れられる場合、このPerlスニペットが機能します。

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

ONLY KISS SHORT PURE SED

これをするつもりです

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

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

0
mr.tee