sedの置換で、BSD sedかGNU sedかを気にせずにワンライナーで改行(\n)文字を出力する方法。
動機
sed
のs
(置換)コマンドでの置換(後)の文字列内で改行を出力する方法については、色々な解説記事がある。
ワンライナーで処理するやり方として、これらで挙げられている方法では、
-
BSD sed
(macOS標準)と、GNU sed
でやり方が異なっていると、環境によって実装を使い分けないといけない。 - シェル変数に改行コードを含める方法は、
csh/tcsh
だとちょっと難しい。 - シェル変数に改行コードを含める方法は、
Makefile
の中などの改行コードが別途展開されてしまうような場合には、そのままではうまくいかない。(make
との相性)
そのため、より汎用的な方法を考えることにした。
例題
単にtr
でも実現できる例ではつまらないので、ここでは、kani:wani:uni:hebi:oni
という文字列の、wani
とuni
の間の:
だけを改行に置換する方法を考える。
BSD sed
では、s
コマンドの第2引数に\n
と書いても改行コードとは解釈されない。マニュアルによると'\'に続けて改行を入れればよい、と書いてある。
% echo "kani:wani:uni:hebi:oni" | sed -e "s/wani:uni/wani\\
> uni/g"
kani:wani
uni:hebi:oni
(>
はシェルの継続行のプロンプト。) この方法は、GNU sed
でもうまくいく。(以下、gsed
はmacportsでインストールされたGNU sed
)
% gsed --version
gsed (GNU sed) 4.8
% echo "kani:wani:uni:hebi:oni" | gsed -e "s/wani:uni/wani\\
> uni/g"
kani:wani
uni:hebi:oni
これをワンライナーで収めようと思うと問題に突き当たる。sed
に、複数行にわたるsed
コマンドを渡す際に、BSD sed
と GNU sed
で挙動が異なる。BSD sed
では、-e
のオプション引数を改行して繋いでから実行しているようで、
% echo "kani:wani:uni:hebi:oni" | sed -e "s/wani:uni/wani\" -e"uni/g"
kani:wani
uni:hebi:oni
とすれば、ワンライナー化してうまく行く。一方でGNU sed
は、-e
のオプションをそれぞれを解釈して順次実行しているようで、
% echo "kani:wani:uni:hebi:oni" | gsed -e "s/wani:uni/wani\" -e"uni/g"
gsed: -e expression #1, char 16: `s' コマンドが終了していません
とエラーになる。さて、どうしたものか。
発想の転換
sed
に改行を渡すのが難しいので、なんとかしてsed
の中で改行を埋め込む方針で考えてみる。そこでG
コマンドを使ってみる。ホールドスペースが空なら、パターンスペースの末尾に改行を追加してくれる。そうすればパターンスペースの末尾に来た改行文字を正規表現でマッチさせて、置換文字列の所望の所で展開させればよい。
% echo "kani:wani:uni:hebi:oni" | sed -e "G;s/\(.*wani\):\(uni.*\)\(\n\)/\1\3\2/g"
kani:wani
uni:hebi:oni
% echo "kani:wani:uni:hebi:oni" | gsed -e "G;s/\(.*wani\):\(uni.*\)\(\n\)/\1\3\2/g"
kani:wani
uni:hebi:oni
この方法ならばBSD sed
でGNU sed
でも共通に動作するワンライナーになった。しかしこの置換では、sed
のs
コマンドにg
オプションが付いているが、一行に複数箇所該当した場合には期待する動作にならないので、もう一工夫必要である。
% echo "kani:wani:uni:hebi:wani:uni:oni" | sed -e "G;s/\(.*wani\):\(uni.*\)\(\n\)/\1\3\2/g"
kani:wani:uni:hebi:wani
uni:oni
% echo "kani:wani:uni:hebi:wani:uni:oni" | gsed -e "G;s/\(.*wani\):\(uni.*\)\(\n\)/\1\3\2/g"
kani:wani:uni:hebi:wani
uni:oni
解決策。
sed
の処理としては複雑になってしまうが、ループ処理することにする。BSD sed
では、ラベルの定義や、b
,t
コマンドの引数にラベルを与える場合にはそのあとに改行を入れることが必須なので、ラベル定義部は独立に-e
オプションの引数とする必要がある。
** 2020/5/2 追記 **
入力行が複数行で、該当しない行ではG
コマンドが実行されないようにアドレス指定する必要がある。
% echo "kani:wani:uni:hebi:wani:uni:oni" \
| sed -e ":t" -e "/wani:uni/{G; s/\(.*wani\):\(uni.*\)\(\n\)/\1\3\2/;};/wani:uni/bt"
kani:wani
uni:hebi:wani
uni:oni
% echo "kani:wani:uni:hebi:wani:uni:oni" \
| gsed -e ":t" -e "/wani:uni/{G;s/\(.*wani\):\(uni.*\)\(\n\)/\1\3\2/;};/wani:uni/bt"
kani:wani
uni:hebi:wani
uni:oni
同じようなアドレス指定を2回書いていたりして、ややスマートさに欠けるような気がするものの、BSD sed
でGNU sed
でも共通に動作する改行文字に置換するワンライナーが得られた。この方法ならばMakefile
の中でも使えそうだ。
蛇足
他のsedコマンドと組み合わせて使用する場合には、ホールドスペースが空でない場合が考えられる。その場合には、s/\(.*wani\):\(uni.*)\(\n\)/\1\3\2/;
のところのマッチさせる正規表現を変更する必要がある。とくにホールドスペースが複数行の文字列(改行を含む)の場合には注意が必要と思われる。
Author And Source
この問題について(sedの置換で、BSD sedかGNU sedかを気にせずにワンライナーで改行(\n)文字を出力する方法。), 我々は、より多くの情報をここで見つけました https://qiita.com/Nanigashi_Uji/items/25e706919f7c02b9f0d4著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .