
sed
この質問は以前にされたことがないと思うので、これが可能かどうかはわかりません。
文章の中にたくさんの数字があり、それを単語に展開する必要があるとします。実際の例としては、典型的なエッセイの番号付き引用を MLA 形式に置き換えることが挙げられます。
essay.txt
:
Sentence 1 [1]. sentence two [1][2]. Sentence three[1][3].
Key.txt
(これはタブ区切りのファイルです):
1 source-one
2 source-two
3 source-three
...etc
期待されるResult.txt
:
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]
ここに私の疑似コードの試みがありますが、十分に理解していないsed
か、tr
正しく実行できません。
cat essay.txt | sed s/$(awk {print $1} key.txt)/$(awk {print $2} key.txt)/g
PS: 複数の用語を使用して一括検索と置換を行うためのコツが Notepad++ にあれば、とても便利です。現状では、検索と置換は一度に 1 つの用語に対してのみ機能するようです。しかし、一度に多数の用語に対して一括して実行する方法が必要です。
答え1
代わりに以下を使用してくださいperl
:
$ perl -ne '
++$nr;
if ($nr == $.) {
@w = split;
$k{$w[0]} = $w[1];
}
else {
for $i (keys %k) {
s/(\[)$i(\])/$1.$k{$i}.$2/ge
}
print;
}
close ARGV if eof;
' key.txt essay.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]
答え2
awk
perl
ここでも効果的に同じことができるもう少しシンプルにただし、GNU 以外の実装では、(大きな?) テキスト ファイルを不必要に分割して CPU 時間を少し浪費する可能性があります。
awk 'NR==FNR{a["\\["$1"\\]"]="["$2"]";next} {for(k in a) gsub(k,a[k]);print}' key.txt essay.txt
あなたが頼んだので説明:
awk
パターンとアクションのペアからなる「スクリプト」を受け取り、1 つ以上のファイル (または標準入力) から 1 レコードずつ読み取ります。デフォルトでは各レコードは 1 行で、各レコードはデフォルトで空白 (タブを含む) でフィールドに分割され、スクリプトが順番に適用されます (特に指示がない限り)。パターン (多くの場合、現在のレコードやそのフィールドを参照) をテストし、一致する場合はアクション (多くの場合、前述のレコードやフィールドに対して何らかの処理を実行します) を実行します。ここでは 2 つのファイルを指定しkey.txt essay.txt
、その順序で 1 行ずつ読み取ります。スクリプトできるコマンドラインではなくファイルに記述することもできますが、ここではそうしないことを選択しました。最初のパターンは です
NR==FNR
。NR
は組み込み変数で、処理中のレコードの番号です。 はFNR
同様に現在の入力ファイル内のレコードの番号です。 最初のファイル (key.txt
) ではこれらは等しくなりますが、2 番目のファイル (および他のファイル) ではこれらは等しくありません。最初のアクションは です
{a["\\["$1"\\]"]="["$2"]";next}
。awk
には「連想」または「ハッシュ」配列があり、arrayname[subexpr]
はsubexpr
文字列値式で、配列の要素を読み取りまたは設定します。$number
たとえば$1 $2
、 などはフィールドを参照し、$0
はレコード全体を参照します。 上記のとおり、このアクションは の行に対してのみ実行されるため、key.txt
たとえばそのファイルの最後の行には$1
があり3
、$2
は であり、これは の添え字と の内容source-three
を持つ配列エントリを格納します。これらの値を選択した理由については、以下を参照してください。と はエスケープを使用する文字列リテラルで、実際の値は と ですが、は単なる であり、間に演算子のない文字列オペランドは連結されます。最後にこのアクションが実行され、これはこのレコードのスクリプトの残りの部分をスキップし、ループの先頭に戻って次のレコードを開始することを意味します。\[3\]
[source-three]
"\\["
"\\]"
\[
\]
"[" "]"
[ ]
next
2番目のパターンは空なので、2番目のファイルのすべての行に一致し、アクションを実行します
{for(k in a) gsub(k,a[k]);print}
。このfor(k in a)
構造は、Bourne型シェルが行うのとほぼ同じようにループを作成しますfor i in this that other; do something with $i; done
が、ここでは の値k
が下付き文字配列 のa
。このような値ごとに、gsub
(global replace) が実行され、指定された正規表現に一致するすべての一致が検索され、指定された文字列に置き換えられます。配列 の添え字と内容 (上記) を選択したので、たとえば は\[3\]
テキスト文字列に一致する正規表現で[3]
、[source-three]
はそのような一致ごとに置換するテキスト文字列です。 は、デフォルトでgsub
現在のレコードに対して動作します$0
。 内のすべての値に対してこの置換を行った後、 がa
実行され、print
デフォルトでは$0
必要な置換がすべて行われた状態で現在の状態が出力されます。
注: GNU awk (gawk) は、特に Linux では一般的ですが、汎用的ではありません。パターンや実行されるアクションにフィールド値が必要ない場合は、実際にはフィールド分割を行わないという最適化が行われます。他の実装では、少量の CPU 時間が無駄になる場合がありますが、cuonglm のperl
方法でこれを回避できます。ただし、ファイルが巨大でない限り、これはおそらく気付かないでしょう。
答え3
bash$ sed -f <( sed -rn 's#([0-9]+)\s+(.*)#s/\\[\1]/[\2]/g#p' key.txt ) essay.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].
答え4
これを実現するには、ループ内でインプレース sed 置換を使用できます。
$ cp essay.txt Result.txt
$ while read n k; do sed -i "s/\[$n\]/\[$k\]/g" Result.txt; done < key.txt
$ cat Result.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].