あるファイルで定義されたコンテンツを別のファイルに置き換える方法

あるファイルで定義されたコンテンツを別のファイルに置き換える方法

はじめに:この問題は数日に1回出てきます。解決は簡単ですが、sed説明には時間がかかります。後でこの一般的な解決策を参照し、特定の状況への適応のみを説明できるように、この質問と回答を作成しています。自由に貢献してください。

変数定義を含むファイルがあります。変数は大文字または下線で構成され、_その値はに配置されます:=。これらの値には他の変数を含めることができます。これはGnom.def

NAME:=Gnom
FULL_NAME:=$FIRST_NAME $NAME
FIRST_NAME:=Sman
STREET:=Mainstreet 42
TOWN:=Nowhere
BIRTHDAY:=May 1st, 1999

form.txtその後、テンプレート形式を含む別のファイルがあります。

$NAME
Full name: $FULL_NAME
Address: $STREET in $TOWN
Birthday: $BIRTHDAY
Don't be confused by $NAMES

フォーム($および識別子で示されている)の変数を別のファイルの定義に再帰的に置き換えて、次のテキストを取得できるスクリプトが必要です。

Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES

最後の行は、変数の部分文字列が誤って変更されないようにすることです。

答え1

この種の問題を解決するための基本的なアイデアは、2つのファイルをsedスペアスペースsed。その後、他のファイルの各行に予約されたスペースが追加され、追加された定義で繰り返されるすべての変数が置き換えられます。

スクリプトは次のとおりです。

sed '/^[A-Z_]*:=.*/{H;d;}
  G
 :b
 s/$\([A-Z_]*\)\([^A-Z_].*\n\1:=\)\([^[:cntrl:]]*\)/\3\2\3/
 tb
 P
 d' Gnom.def form.txt

ここで詳細に説明する。

/^[A-Z_]*:=.*/{H;d;}

これは定義を予約済みスペースに収集します。/^[A-Z_]*:=.*/変数名とシーケンスで始まるすべての行を選択します:=。この行で{}次のコマンドを実行します。H予約済みスペースに追加してd削除して再起動すると、印刷されません。

定義ファイルのすべての行がこのパターンに従うことを確認できない場合、または他のファイルの行が指定されたパターンと一致する可能性がある場合は、後で説明するようにこのセクションを調整する必要があります。

G

この時点で、スクリプトは2番目のファイルの行のみを処理します。予約済みGスペースはパターンスペースに追加されるため、パターンスペースのすべての定義を改行で区切って処理する必要があります。

:b

これでサイクルが始まります。

 s/$\([A-Z_]*\)\([^A-Z_].*\n\1:=\)\([^[:cntrl:]]*\)/\3\2\3/

コア部品の交換です。今、私たちは次のようなものを持っています

At the $FOO<newline><newline>FOO:=bar<newline>BAR:=baz
       ----==================---  ###

パターン空間で。 (詳細:最初の定義の前には2つの改行文字が続きます。1つは予約済みスペースに追加して作成され、もう1つはバッファースペースに追加することによって生成されます。)

----下線付きの部分を表示するには、一致を使用してください$\([A-Z_]*\)。これにより、\(\)後で文字列を再参照できます。

\([^A-Z_].*\n\)===逆参照前のすべての項目である下線付きの部分と一致します\1。 n個の変数文字で始まる場合、変数の部分文字列は一致しません。逆参照を改行文字で囲み、定義された部分:=文字列が一致しないことを確認してください。

最後に\([^[:cntrl:]]*\)一致する###部分である定義です。定義に制御文字がないとします。可能であれば、[^\n]GNUを使用するsedか、POSIXの回避策を作成できますsed

$変数名と変数名は変数値に変わり、中間\3部分と定義はそのまま残ります\2\3

 tb

置換がすでに行われている場合、tコマンドはマーカーを繰り返してb別の置換を試みます。

 P

これ以上置換できない場合、大文字はP最初の改行まですべてを印刷します(したがって定義部分は印刷されません)。

 d

パターン空間が削除され、次のループが始まります。完璧。

限定

  • FOO:=$BAR定義ファイルに含めたり、BAR:=$FOOスクリプトを永久に繰り返すなど、不快な操作を実行する可能性があります。これを防ぐために処理順序を定義できますが、これによりスクリプトを理解するのがより困難になります。スクリプトに愚かな証明が必要ない場合は、そのままにしてください。

  • 定義に制御文字を含めることができる場合は、G改行文字を別の文字に置き換えてy/\n#/#\n印刷する前にこれを繰り返すことができます。より良い解決策がわからない。

  • 定義ファイルに別の形式の行を含めることができるか、別のファイルに定義されている形式の行を含めることができる場合は、定義ファイルの最後の行または別の行として2つのファイル間で一意の区切り文字を使用する必要があります。ファイルまたはsed他のファイル間で渡される別々のファイルとして使用されます。次に、区切り線が満たされるまで定義を収集し、他のファイルの行を繰り返すループがあります。

答え2

sedスクリプトと比較するために、POSIX awkスクリプトは次のとおりです。

$ cat tst.awk
BEGIN { FS=":=" }
NR==FNR {
    map["$"$1] = $2
    next
}
{
    mappedWord = 1
    while ( mappedWord ) {
        mappedWord = 0
        head = ""
        tail = $0
        while ( match(tail,/[$][[:alnum:]_]+/) ) {
            word = substr(tail,RSTART,RLENGTH)
            if ( word in map ) {
                word = map[word]
                mappedWord = 1
            }
            head = head substr(tail,1,RSTART-1) word
            tail = substr(tail,RSTART+RLENGTH)
        }
        $0 = head tail
    }
    print
}

$ awk -f tst.awk Gnom.def form.txt
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES

スクリプトは、使用する文字や文字列を気にしません(:=2番目のファイルには存在せず、存在しない制御文字にはっきりと依存するsedバージョンとは異なり)。match()、現在[$][[:alnum:]_]+

再帰的定義が与えられた場合、上記のアプローチは失敗しますが、必要に応じて、次の簡単な調整を使用して、それを検出、報告、および賢明に処理できます。

$ head Gnom.def form.txt
==> Gnom.def <==
FOO:=$BAR
BAR:=$FOO
NAME:=Gnom
FULL_NAME:=$FIRST_NAME $NAME
FIRST_NAME:=Sman
STREET:=Mainstreet 42
TOWN:=Nowhere
BIRTHDAY:=May 1st, 1999

==> form.txt <==
$NAME
Full name: $FULL_NAME
Address: $STREET in $TOWN
Birthday: $BIRTHDAY
Don't be confused by $NAMES
testing recursive $FOO
testing recursive $BAR

$ cat tst.awk
BEGIN { FS=":=" }
NR==FNR {
    map["$"$1] = $2
    next
}
{
    mappedWord = 1
    iter = 0
    delete mapped
    while ( mappedWord ) {
        if ( ++iter == 100 ) {
            printf "%s[%d]: Warning: Breaking out of recursive definitions.\n", FILENAME, FNR | "cat>&2"
            break
        }
        mappedWord = 0
        head = ""
        tail = $0
        while ( match(tail,/[$][[:alnum:]_]+/) ) {
            word = substr(tail,RSTART,RLENGTH)
            if ( word in map ) {
                word = map[word]
                mappedWord = 1
            }
            head = head substr(tail,1,RSTART-1) word
            tail = substr(tail,RSTART+RLENGTH)
        }
        $0 = head tail
        for (word in mapped) {
            mapped[word]++
        }
    }
    print
}

$ awk -f tst.awk Gnom.def form.txt
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES
testing recursive $BAR
testing recursive $FOO
form.txt[6]: Warning: Breaking out of recursive definitions.
form.txt[7]: Warning: Breaking out of recursive definitions.

上記の警告はstdoutではなくstderrとして印刷されるため、出力はめちゃくちゃになりません。

$ awk -f tst.awk Gnom.def form.txt 2>err
Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES
testing recursive $BAR
testing recursive $FOO

$ cat err
form.txt[6]: Warning: Breaking out of recursive definitions.
form.txt[7]: Warning: Breaking out of recursive definitions.

関連情報