sed: 構成ブロックの複数行の置換

sed: 構成ブロックの複数行の置換

デフォルトでは、次の構成ファイルがあります。

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
(... more content ...)
# END DYNAMIC BLOCK
(... even more content ...)

これでb​​ashでこれを使ってCONTENT=`wget -O - http://$SERVER/get_config.php`動的ブロックを置き換えました。

これで、代替操作を実行する方法と、そのブロックが存在しない場合は、ファイルの末尾にそのブロックを挿入するスクリプトを取得する方法は何ですか?

答え1

sedを使用するには、名前付きパイプから読み取ることができます。このコードはエラーを処理しません。動的ブロックヘッダーが複数回表示されると、スクリプトはブロックされます。

CONTENT_URL="http://$SERVER/get_config.php"
tmp=$(mktemp -d)
(
  cd "$tmp"
  mkfifo dynamic_seen dynamic_content
  : >dynamic_seen & seen_pid=$!
  wget -O dynamic_content "$CONTENT_URL" & wget_pid=$!
  sed -e '/^# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY$/ p' \
      -e '/^# END DYNAMIC BLOCK$/ {'
          -e p -e 'r dynamic_seen' -e 'r dynamic_content' -e '}' \
      -e '/^# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY$/, /^# END DYNAMIC BLOCK$/ d'
  if ! kill $dynamic_seen 2>/dev/null; then
    # The pipe hasn't been read, so there was no dynamic block. Add one.
    echo "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY"
    cat dynamic_pipe
    echo "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY"
  fi
)
rm -rf "$tmp"

しかし、私はawkを選択します。

export CONTENT_URL="http://$SERVER/get_config.php"
awk '
    $0 == "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY" {skip=0; system("wget \"$CONTENT_URL\""); substituted=1}
    !skip {print}
    $0 == "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY" {skip=1}
    END {
         if (!substituted) {
            print "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY";
            system("wget \"$CONTENT_URL\"");
            print "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY";
        }
    }
'

答え2

次のように、サブシェルと2つのsedコマンドを使用します。

beg_tag='# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY'
end_tag='# END DYNAMIC BLOCK'

(
  sed "/^$beg_tag"'$/,$d' oldconf
  echo "$beg_tag"
  wget -O - http://$SERVER/get_config.php
  echo "$end_tag"
  sed "1,/^$end_tag/d" oldconf
) > newconf

beg_tag合計にsedの重要な文字を入れないように注意してくださいend_tag

タグがない場合、出力が追加されます。最初の sed コマンドは入力から行を削除せず、2 番目の sed コマンドはすべての行を削除します。

テスト

以下が含まれている場合oldconf

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
(... more content ...)
# END DYNAMIC BLOCK
(... even more content ...)

そしてwgetコマンドはに置き換えられ、echo hello world出力は次のようになります。

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
hello world
# END DYNAMIC BLOCK
(... even more content ...)

今ブロックを削除すると、次の入力を使用します。

(...content...)
(... even more content ...)

出力は次のとおりです

(...content...)
(... even more content ...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
hello world
# END DYNAMIC BLOCK

答え3

これはsed実際には非常に簡単です。行とアンカーの間の範囲のバランスをEOFに調整するだけです。

INPUT |
sed -e 's/\\/&&/g;$!s/$/\\/' |        #this sed escapes INPUT for scripting
sed -e '/^'"$START"'/,$!{$!b          #this sed applies concatenated scripts
             G;G;s/$/'"$END"'/;P;:n
};$!N;  /\n'"$END"'/,$!{G;$!bn
};      /\n\n/c\' -f - -e 'P;$d;D
' ./named_infile >outfile

したがって、いくつかのことが進行中ですが、その中で最も重要なことは次のとおりです。

/^$START/,$!{ -- function --}
N; /\n$END/,$!{ -- function -- }

アイデアは私たちがアンカーラインの範囲はラインである1か、$基本的には終わりました。貪欲。一般に、行範囲は、可能な最小の行サブセットにのみ適用されます。各 LHS マッチングから始まり、入力で次に発生する最初の RHS マッチングで終了します。 RHSがEOFの場合は、1つのみ適用できます。なぜなら、RHSがEOFであるからである。

私がするとき:

/^$START/,$!{ -- function -- }

中かっこの間のすべてのコードがinfileのすべての行に対して実行されるように指定しましたが、いいえ含む$START。この関数コンテキストでは、b最後の行ではなくすべての行を展開します。!$

これにより、入力の最初の行の前のすべての行が$START自動的に印刷され、無視されます。ただし、$最後の行がこの範囲内にある場合(一度も表示されない可能性があるため)、串に文字を$STARTリンクする準備が整いました。c

したがって、範囲が入力に表示されない場合は、INPUTがファイルの末尾に追加されます。

次に私がこれをするとき:

N; /\n$END/,$!{ -- function -- }

今回もコンテキストに従って関数を適用します。今回は範囲の本文に適用され、入力が初めて表示される場合にのみ適用されます。なぜなら、の補数は最初の発生前にソートされていないすべての行で/\n$END/,$あり、次の発生までのみ適用されますが、次の発生は含まれないからです。b$START$END

この場合、適用される関数は分岐ループです。入力が範囲内にある限り、最初の一致が見つかるまで引き続き逆追跡し、拡張行をプルし、bこの時点で標準入力スクリプトファイルの範囲全体を一時停止します。 - またはエスケープされた入力。最初の一致の前に最後の行が発生した場合、最後の行にも同じルールが適用されます。N$ENDc-f -$START

それはすべてです。ただし、この操作には特別なファイルは必要ありません。(安全に)コピーを含む入力する必要に応じて適用するためにスクリプト内でrいつでも作成する必要はありません。

関連情報