区切られたテキストデータがあり、"|"
列値を変換したいと思います。
$ cat infile
Mark|father
Jason|SOn
Jose|son
Steffy|daugHter
(父|息子|娘)の事例を無感覚に検索し、父が父の場合、息子が息子の場合、娘が娘の場合を変えたい。
したがって、出力ファイルは次のようになります。
$ cat outfile
Mark Father
Jason Son
Jose Son
Steffy Daughter
IGNORECASEとsubまたはgsubのさまざまな組み合わせを試していますが、すべてのエントリがinfileとして印刷されます。
答え1
これは試された答えです質問の元のバージョン。それ以来、要件が変更されました。
sed
GNU実装の利点の1つは次のとおりです。
$ sed -E 's/(^|\s)(son|daughter|father)(\s|$)/\1\L\u\2\3/i' < file
Mark Father
Jason Son
Jose Son
Steffy Daughter
正規表現は、これら3つの単語のいずれかに一致しますが、その単語の前に空白以外の文字がない場合にのみ一致します。
\L
単語全体を小文字に変換し、最初の文字だけを大文字\u
に変換します(これはex
70年代のものですが、vi
残念ながらparまでではありませんsed
)。
perl -pe
代わりに、同じものを使用できます(GNUよりもsed -E
多くのシステムで潜在的に移植性が高い)。ただし、次のように単純化できます。perl
sed
perl
perl -pe 's/(?<!\S)(son|daughter|father)(?!\S)/\L\u$&/i'
つまり、これらの文字列がスペースで区切られた長い単語(Jason
入力など)の一部ではないことを確認するには、負のナビゲーション演算子を使用します。 sed の\b
inperl
および word 境界演算子も参照してください。しかし、これは文字を構成する単語ではないので、孫を孫に変えるのと似ています。\<
\>
(?!\w)
-
各行は最大1回だけ変更できます。すべての項目を置き換えるには、g
上記のフラグにフラグを追加できますperl
。最初の一致が次に置き換えられ、検索が続行され、以前の一致が見つからないため、1つに追加するとsed
一部が失われる可能性があります。この問題は、事前にすべての空白文字を倍増し、後で復元することで解決できます。Mark son SON sOn
" son "
" Son "
sed
"SON sOn"
\s
SON
sed -E 's/\s/&&/g
s/(^|\s)(son|daughter|father)(\s|$)/\1\L\u\2\3/ig
s/(\s)\1/\1/g'
しかし、これは少し複雑すぎるようになりました。
答え2
効率性と堅牢性のために、正規表現の比較と* sub()の代わりにハッシュルックアップを使用します(正規表現のメタ文字や逆参照を含む文字列または他の文字列サブ文字列を使用することにした場合)。
$ cat tst.awk
BEGIN {
FS = "|"
split("Father|Son|Daughter",tmp)
for (i in tmp) {
map[tolower(tmp[i])] = tmp[i]
}
}
{ lc = tolower($2) }
lc in map {
$2 = map[lc]
}
{ print }
$ awk -f tst.awk file
Mark Father
Jason Son
Jose Son
Steffy Daughter
答え3
(すべてのawk実装に適用される)1つのアプローチは、2番目の列を小文字にし、最初の文字のみを大文字にして一致することを確認し、2番目の列の値を保存された変換内容に更新することです。tmp。
$ awk -F'|' '{ tmp=toupper(substr($2,1,1)) tolower(substr($2,2)) }
tmp ~ /^(Father|Son|Daughter)$/ { $2=tmp }1' infile
Mark Father
Jason Son
Jose Son
Steffy Daughter
(GNU awk固有)を使用する場合、IGNORECASE
これは置き換えるのではなく、実行したいすべての一致処理(文字列/正規表現)にのみ適用されます。
答え4
Raku(以前のPerl_6)の使用
raku -pe 's:i:g/ «father» | «daughter» | «son» /{$/.tclc}/;'
または
raku -pe 's:i:g/ «father» | «daughter» | «son» /{$/.wordcase}/;'
正規表現副詞は、:ignorecase
Raku(略語)で大文字と小文字を区別しない一致を実行します。:i
左右の単語の境界は、単語全体のみが一致することを保証します(つまり、同様の出力が発生する可能性がある偽の一致はありません«
)。左の単語の境界には置換を使用し、右の単語の境界には置換を使用できます。»
JaSon
<<
«
>>
»
大文字と小文字を変更するために、Rakuにはwordcase
単語を取り、最初の文字を大文字に置き換え、最初の文字ではなくすべての文字を小文字に変換する素晴らしいルーチンがあります。 [Raku機能tclc
(文字通り「titlecase-lowercase」)は基本的に同じことを行いますが、オプションは少なくなります。]
入力例:
Mark|father
Jason|SOn
Jose|son
Steffy|daugHter
Agnes|moTHer
出力例:
Mark|Father
Jason|Son
Jose|Son
Steffy|Daughter
Agnes|moTHer
たとえば、OPが区切り文字に分割したい場合は、|
次のRakuの1行文字を呼び出すだけです。今後または後ろに上記のコード:
raku -ne '.split("|").put;'
出力例:
Mark Father
Jason Son
Jose Son
Steffy Daughter
Agnes moTHer
付録:
@Stéphane Chazelasは、上記のコード(たとえば)でハイフンで連結された単語が内部の大文字(たとえばgod-son
to god-Son
)を持つことを注釈で指摘しました。以下のコードは、この問題を回避するために3つのリテラル一致を使用しています。
raku -ne '.wordcase(:where({ $_.fc eq "father" | "daughter" | "son"})).put;'
または
raku -pe '.=wordcase(:where({ $_.fc eq "father" | "daughter" | "son"}));'