
次のテキストファイルがあります。
banana
apple
juice
mango
something
パターン を検索しておりjuice
、その一致するパターンの 2 行目を逆順 (つまり、一致するパターンの 2 行上) で見つけて、 に置き換えたいと考えていますcoconut
。
期待される出力:
coconut
apple
juice
mango
something
次のように試してみましたが、上記の 2 行だけが削除され、探している行は削除されませんでした。
tac foo.txt |sed '/juice/I,+2 d' |tac
mango
something
上記のスクリプトを微調整すればうまくいくと思いますが、確信はありません。
注: 一致は再出現しません。また、完全一致である必要もありません (つまり、長い行でも一致が見つかる可能性があります)。一致では大文字と小文字が区別されます。
答え1
問題がなければed
、ストリームではなくファイルを編集する必要があり、次の 1 つだけです juice
。
$ more <<-EOF | ed -s ./tmp.txt
/juice/
-2
c
coconut
.
w
q
EOF
$
行を見つけて、2行上に進み、c
hange、
w
rite、 q
uit を探します。
コメントで @d-ben-knoble が提案した、さらにコンパクトなバリエーション:
$ printf '%s\n' '/^juice$/-2s/.*/coconut/' w q | ed -s ./tmp.txt
答え2
あなたのアプローチに従って、
tac file|sed '/juice/{n;n;s/.*/coconut/}'|tac
/juice/
を含む行に一致しますjuice
。n;n;
現在の行と次の行を出力します。s/.*/coconut/
置換を行います。
-z
どうやらGNU sedをお持ちのようですので、ファイル全体をメモリに取り込み、juiceの2行目を直接編集することもできます。
sed -rz 's/[^\n]*(\n[^\n]*\n[^\n]*juice)/coconut\1/' file
[^\n]
は「改行ではない」ことを意味し、括弧は後方参照()
によって再現されたグループをキャプチャします\1
。
答え3
$ tac file | awk 'c&&!(--c){$0="coconut"} /juice/{c=2} 1' | tac
coconut
apple
juice
mango
something
答え4
ここで、さらに別のawk
アプローチ、今回はダブルパス方式を紹介します。
awk 'NR==FNR&&/juice/{m=FNR} NR>FNR&&FNR==m-2{$0="coconut"} (NR>FNR)' file file
- ファイルを指定します二度コマンドライン引数として、2 回処理されることになります。
- 最初のパス(
FNR
ファイルごとの行カウンタ はNR
グローバル行カウンタ に等しい)では、検索パターンがどの行にjuice
出現するかを識別し、それを変数 に格納しますm
。 - 2 回目のパスでは、
m-2
置換テキストに行番号を設定しますcoconut
。 - 一般的なルールとして、変更を含む行を出力しますが、2 番目のパス (条件が
NR>FNR
「true」と評価されるパス) でのみ出力します。
GNU をお持ちの場合awk
(他のawk
実装でもこれをサポートしています)、次のコマンドを使用して、一致が見つかったらすぐに最初のパスを中止することで、プロセスを少し高速化できますnextfile
。
awk 'NR==FNR&&/juice/{m=FNR;nextfile} NR>FNR&&FNR==m-2{$0="coconut"} (NR>FNR)' file file