• ベストアンサー

awk 正規表現を使って置換

あるファイルの中の2列目に含まれる "ab" "ac" "ae" という文字列をそれぞれ "zb" "zc" "ze"に置き換えたいのですが、awkまたはsedで正規表現を使って効率のいい方法はないでしょうか。 awk ' { gsub(/ab/,"zb",$2); gsub(/ac/,"zc",$2); gsub(/ae/,"ze",$2); print }' testfile でやりたいことはできるのですが、「aの後にb,c,eが続く場合にaをzに置換する」というアイディアを使えばもっと効率のよいスクリプトが書けるはず、と思いつつ、awkの勉強を始めたばかりでなかなか思い浮かびません。 testfileの中身は以下の通り: abcde abaab aaaae acbec accee adabd dceba aeecs hhhgf sbacc 以下のような出力を望んでいます: abcde zbazb aaaae zcbec accee adzbd dceba zeecs hhhgf sbzcc awk ' BEGIN { var = "[bce]" } { gsub("a"var,"z"var,$2); print }' だと "zb" "zc" "ze"ではなくすべて"z[bce]"に置き換わってしまうし、 awk ' BEGIN { var = "[bce]" } { gsub("a"var,"z&",$2); print }' だと"zab" "zac" "zae"になってしまうし… まずはawk,sedで勉強したいと思っていますが、それ以外でもいい案がありましたら教えてください。よろしくお願いします。

  • drmel
  • お礼率100% (4/4)

質問者が選んだベストアンサー

  • ベストアンサー
  • kumoz
  • ベストアンサー率64% (120/185)
回答No.4

中間の空白が半角スペース1つに見えたものですから、タブとは思いませんでした。 最初のコードを少し変更してみました。[^ ] と [ ] は半角スペース1つにに見えるか も知れませんが、半角スペースとタブの両方を入れてください。これで、中間の空白が 半角スペースまたはタブ1つに対応できると思います。 sed ":loop; s/a\([bce]\)\([^ ]*\)$/z\1\2/; /[ ].*a[bce]/t loop" testfile 上のコードを行を分けて書き直すと、次のようになります。 :loop s/a\([bce]\)\([^ ]*\)$/z\1\2/ /[ ].*a[bce]/t loop 最後の行の /.../t loop というのは、正規表現にマッチする場合はラベル loop にジャンプ せよという意味です。正規表現に指定してあるのが 「空白文字に続く文字列中に a[bce] が 存在する場合」ですので、2番目の文字列中の置き換えが終了するまでループが続きます。 Perl の場合は、もっと簡単に書くことができます。試してみてください。 perl -ple "s/a([bce])(?!.*\s)/z$1/g" testfile

drmel
質問者

お礼

ありがとうございます!!! Perlのスクリプトは、1番目の方の回答をいただいてから自分なりに挑戦していたのですが、何行にも渡った挙句、なかなか目的が達成できずにいました。それが、こんなシンプルな1行でできてしまうとは、目からウロコです。 sedのloopに関してもとてもわかりやすくご説明いただきありがとうございました。よく理解できました。 同じことを実現するのでも、言語によって記述が違うだけでなくアプローチも違うのですね。これからもっといろいろ勉強しようと意欲がわいています。皆さんありがとうございました。

その他の回答 (3)

  • kumoz
  • ベストアンサー率64% (120/185)
回答No.3

ループを使ってみました。 sed ":loop; s/a\([bce]\)\([^ ]*\)$/z\1\2/; / .*a[bce]/t loop" testfile

drmel
質問者

お礼

ありがとうございます。 でもこれだと各行最初に出てきたab ac aeのみが置き換わってしまうようです… 例えば最初の行はabcde zbazbではなくzbcde abaabになってしまいます。

drmel
質問者

補足

先ほど下の欄に「回答へのお礼」を記入したのですが、その後試行錯誤してみた結果、 sed ":loop; s/a\([bce]\)\([^ ]*\)$/z\1\2/; / .*a[bce]/!t loop" testfile [^ ] をスペースではなくタブに変換し、t loopを !t loopに変換したところうまくいきました!loopの仕組みがよくわかっていないのですが、/ .*a[bce]/でなくなったら「:loop」に戻る、つまりその行にabもacもaeもなくなったら行頭に戻る、という意味でしょうか?(←よくわかっていない...)

noname#227025
noname#227025
回答No.2

効率がよいかどうかは分かりませんが、sed ならホールドスペースを使えばできます。 例) sed 'h; s/.* \(.*\)/\1/; s/a\([bce]\)/z\1/g; G; s/\(.*\)\n\(.*\) .*/\2 \1/' testfile

drmel
質問者

お礼

早速にご回答ありがとうございます。 sedのホールドスペースというのは知りませんでした。教えていただいたスクリプトそのままだと各行2回ずつ(置換前と置換後)、しかも1列目と2列目で1行ずつずれて出力されてしまいますが、ホールドスペースについてもうちょっと調べて自分で調整してみます。

  • notnot
  • ベストアンサー率47% (4835/10236)
回答No.1

sed であれば、sed 's/a\([bcd]\)/z\1/g' というようなことが出来ますが、こんどは第二カラムだけ対象というのが難しい。Perlのような高機能な物を使う手もあります。 awkだと、このように配列を使うくらいかな。 awk 'BEGIN{split("b c d",a,/ /)}{for(i in a) gsub("a"a[i],"z"a[i],$2);print}'

drmel
質問者

お礼

早速にご回答ありがとうございます。 なるほど、sed 's/a\([bcd]\)/z\1/g'のようなシンプルな形でawkでできればいいなと思っていましたが、awkの場合は配列を使わなければならないのですね。 ご回答いただいてからPerlについてちょっと調べてみましたが、なるほどパワフルなようですね。これまで回りくどい方法で書いてきたスクリプトもすっきりまとめられそう。これから勉強してみようと思います。 Perlだったら2列目だけ置換というのも可能ですか?どのようにすればいいかご教示いただければ幸いです。

関連するQ&A

  • awkで単語単位での置換

    awkを使って単語単位の置換を行いたいのですが、うまく行きません。 awkで単語単位で置換を行うのにはどの様にすれば宜しいのでしょうか? 何方かご存知の方いらっしゃいましたらご教授願います。 具体的にはsedの以下のコマンドと同等の事をawkで行いたいと思っています。 sed 's/\b置換前の単語\b/置換後の単語/g' 例: ・ファイルの内容 hogehoge abcdefg hogehoge hogehoge bcdef hogehoge hogehoge bcd hogehoge hogehoge abcd hogehoge hogehoge bcd hogehoge bcdef bcd hogehoge 上記ファイルをawkで1行ずつ読み込みの3,5,7行目の”bcd”のみ”HIJ”に置き換える事をしたいと考えています。 下記を試してみたのですが、うまく行きませんでした。 1.awk '{gsub(/\bbcd\b/, "HIJ", $0); print $0}' 2.awk '{gsub(/(bcd)/, "HIJ", $0); print $0}' 1だと全く置換されず、2だと3,5,7行目以外も置換されてしまいます。 どの様にしたらうまく行くのでしょうか?

  • awkでデリミタを変更したいです。

    以下のファイルtestを 1111:2222:3333:4444 5555:6666:7777:8888 ↓ 1111,2222,3333,4444 5555,6666,7777,8888 にしてみたいです。 awk -F":" 'BEGIN{OFS=","}{ print }' test などとためしてみたのですがうまくいきません。 ご教示下さい。内容はsedですぐできるんですね、、。

  • awk の使い方

    sample.txt ファイルには   "中村" "08/01/80" "03.1234.5678"   "木村" "08/01/81" "06.1252.2536" のような情報があります。 そこの3番フィールドが電話番号ですので、awkで検索しようと思っています。 それで、 awk -v ARGU="$NUM" ' { VAR=substr($3,2,(length($3)-2)) if ( ARGU == VAR ) {print} }' /sample.txt のようにして、検索をかけようとしましたが、できません。 どこが間違っているのか教えてください。 awkの他の方法があるとか、違うもの(sed or grep)でできるのであれば、お願いします。

  • awkの正規表現について

    ディレクトリ名とファイル名の一覧ファイルがあり、その中から特定ディレクトリの1階層下のディレクトリ名とファイル名を取得したいのですが、うまく抽出できません。 ディレクトリ名とファイル名の一覧ファイル(dir.txt)は /foo/ /foo/bar/ /foo/bar/a.txt /foo/bar/sub/ /foo/bar/sub/x.txt /foo/var/ /foo/var/b.txt /usr/ /usr/bar/ で「/foo/」指定すると /foo/ /foo/bar/ /foo/var/ を抽出し 「/foo/var/」を指定すると /foo/var/ /foo/var/b.txt を抽出したいです。 この場合、awk '$1 ~ /^\/foo\/bar\// {print}' dir.txtとすると /foo/bar/ /foo/bar/a.txt /foo/bar/sub/ /foo/bar/sub/x.txt が抽出されてしまいます。 どのような書き方をすれば1階層下のディレクトリとファイルのみを 抽出できるのでしょうか?

  • awk の使い方

    sample.txt ファイルには   "中村" "08/01/80" "03.1234.5678"   "木村" "08/01/81" "06.1252.2536" のような情報があります。(フィールドの区別はTabです) そこの3番フィールドが電話番号ですので、awkで検索しようと思っています。 それで、 $ shell 06.1252.2536 #!/bin/sh NUM=$1                    #引数をNUMに代入 awk -v ARGU="$NUM" ' { VAR=substr($3,2,(length($3)-2))      #「"」を取り除いた if ( ARGU == VAR ) {print}         #電話番号と比較 }' /sample.txt のようにして、検索をかけようとしましたが、できません。 どこが間違っているのか教えてください。 awkの他の方法があるとか、違うもの(sed or grep)でできるのであれば、お願いします。

  • 2直線が直交する点の求め方が分かりません

    数学で分からない問題があるので質問させていただきます。 3つの点 A(Xa,Ya,Za)、B(Xb,Yb,Zb)、C(Xc,Yc,Zc)与えられているとして、 点A,Bを通る直線ABに、点Cから垂直に線を引く場合に、 2直線の交点D(X,Y,Z)の座標を求める方程式が分かりません。 (Xb-Xa)(X-Xc)+(Yb-Ya)(Y-Yc)+(Zb-Za)(Z-Zc)=0 一つは思いつきましたが、変数が3つあるのであと2つ 式が必要になると思います。 分かる方がいたら教えていただけませんか。 よろしくお願いします。

  • makedbm

    awk 'BEGIN { FS=":"; OFS="\t"; } \ /^[a-zA-Z0-9_}/ { print $1, $0 }' /etc/passwd | \ /ussr/etc/yp/makedbm - $YPDBDIR/'domainname'/passwd.byname $YPDBDIRはNISのデータベースディレクトリ これは/etc/passwdからpasswd.bynameマップを作るときのmakedbmの実行の仕方なんでsyがm awkコマンドがどういう動作をしているのかが、いまいち理解できないでおります。 入力フィールドの区切りを":",出力の区切りをスペースにしているんだと思いますが、 $1,$0は何を表しているのでしょうか。/^[a-zA-Z0-9_}/ は行頭が英数であるものを探しているんでしょうか。

  • UNIX shの文字列の編集に関してのご質問です。

    UNIX shの文字列の編集に関してのご質問です。 添付されている図Aまで、パイプやperlやawkを使用して作成しました。 (例文1) そこから、パイプを使用して、図Bのように3列目($3)の@マークのみ削除をしたいのですが、うまくいきません。 例文1に続けて作成していきたいのですが、図AからBに編集する方法がわからないため、ご教示お願いいたします。 ※ perl または、awkまたはsedを使用可能 例文1 perl -ep **** | awk {print $1 ) | ・・・・・

  • 正規表現置換で

    正規表現をかじりはじめたんですが… 〒000-0000 東京都○○○○ 電話~ を 〒000-0000★東京都○○○○ 電話~ このような置換はできるソフト、 もしくは正規表現記述方法はありますでしょうか? ちなみに正規表現というからには、当然 ・郵便番号はそれぞれべつ ・住所も東京都から始まるとは限らない マッチして欲しいのは、郵便番号と住所の間にある「 」(スペース)だけなのです。 よろしくお願いします。

  • 正規表現での置換について

    以下パターン、JAVA上で正規表を用いて置換したいのですが、可能であれば ご助言いただけないでしょうか。 ■パターン1 置換前:http://www.test.co.jp/aaa.bbb.//rd.test2.co.jp/*http://store.shopping.test.c.jp/index.html 置換後:store.shopping.test.c.jp/index.html →store以下だけにしたい(それより前のものを消したい)と思っています。 ■パターン2 置換前:http://www.test.co.jp/key=22333/ref=123_456_789 置換後:http://www.test.co.jp/key=22333/ →ref以下を消したいと思っています。 よろしくお願いします。

    • ベストアンサー
    • Java