GNU sedでの改行変換
環境:
- Linux
- Bash 4.2.37
- GNU sed version 4.2.1
GNU sedでは改行の出力は\n
を指定してあげるだけで出来ます。
$ echo "aaa\nbbb"|sed "s/\\\\n/\n/"
aaa
bbb
入力の方の\n
を実際の改行文字に変えたいような場合、
出力側に\n
を指定してあげれば良いだけです
1。
BSD sedでの改行出力
環境:
- Mac
- Bash 4.3.24
- BSD sed (May 10, 2005, バージョンの見方が分からない。。。)
同じコマンドを打ってみると
$ echo "aaa\nbbb"|sed "s/\\\\n/\n/"
aaanbbb
な感じでダメです。\n
がn
に解釈されてしまっています。
$ echo "aaa\nbbb"|sed "s/\\\\n/\\n/"
aaanbbb
$ echo "aaa\nbbb"|sed "s/\\\\n/\\\\n/"
aaa\nbbb
となり、まず最初に\n
の方が解釈されてしまっている?感じでしょうか。
$ echo "aaa\nbbb"|sed 's/\\n/\n/'
aaanbbb
$ echo "aaa\nbbb"|sed 's/\\n/\\n/'
aaa\nbbb
シングルクォートでもこんな感じ。
探してみるとまさにこの話についてのものが。
コレ見て思い出しましたが、確かに昔作ったスクリプトでは
何故か\n
で上手くいかない、と思った時に
echo "aaa\nbbb"|sed "s/\\\\n/\\
/"
みたいに書いてたことがありました 2。
最初の頃はGNUとBSDで違いがあることに気づかずに Macで作業してる時に何故か上手くいかないな、とか思って色々やっていた感じ。
上のページではさらにスマートに行うために
LF=$(printf '\\\012_')
LF=${LF%_}
echo "aaa\nbbb"|sed "s/\\\\n/$LF/g"
と、一度改行文字を作ってからそれを使っています。 バックスラッシュでエスケープして実際に改行した改行コードは なんとなく嫌、というのは同意。
上の方法で_
があるのは、
直接改行コードだけを代入してしまうと
代入時に改行コードが文字列の最後にあるとは
トリミングされるらしく結局何も代入してないことになってしまうので、
一度改行コード+適当な文字を代入してから要らない部分を消しています。
ただ、これだと余計な作業が増えるので、なんとか一行に入れようとすると、 まず、
$ echo "aaa\nbbb"|sed "s/\\\\n/$(printf '\\\012')/g"
sed: 1: "s/\\n/\/g": unterminated substitute in regular expression
直接改行文字のところだけ入れようとすると、改行文字のところは無視されて
\
だけに見えてしまってエラーになります。
これを
$ echo "aaa\nbbb"|sed "s/\\\\n/$(printf '\\\012 ')/g"
aaa
bbb
みたいな感じで空白でも何でも一文字後ろに入れてあげれば改行になります。
ので、じゃあこれを消してみれば、というと、
$ echo "aaa\nbbb"|sed "s/\\\\n/$(printf '\\\012_'|sed 's/_//')/g"
sed: 1: "s/\\n/\/g": unterminated substitute in regular expression
な感じになって最終的に$()
の中身で判断されるので途中でごちゃごちゃやっても
意味がないみたいです。
上みたいな代入しか無いなら改行を直接書くほうが良いかな とも思っていましたが、コメントの最後に在る
LF=$'\\\x0A'
echo "hogehoge\nfoo\nbar" | sed 's/\\n/'"$LF"'/g'
を参考にしてもっとスマートなものが出来ました。
BSD sedでのスマートな改行出力
$ echo "aaa\nbbb"|sed s/\\\\n/\\$'\n'/
aaa
bbb
でBSDの場合でもスマートに改行を出力できます。
GNUの場合でもOKです。
なのでなるべくスクリプトでは単に\n
と書く代わりにこちらを
使った方が良いです。
これはsedのコマンドで、クォートなしで$'\n'
を使っているだけ。
$'\n'
と$'\x0A'
(と$'\012'
)は同義です(上の例でLF=$'\\\n
でも同じ)。
クォートなしの場合だとダブルクォートした時と同じようになるので
$
に対して\\$
としているわけですが、
全体をダブルクォートしてしまうと、
$ echo "aaa\nbbb"|sed "s/\\\\n/\\$'\n'/"
aaa$'n'bbb
な感じになってしまいます。
$'\n'
の部分を$VAL
みたいに普通の変数にしてみると
ダブルクォートで囲っても囲まなくてもきちんと変換されるので、
この場合には'\n'
の部分の
シングルクォートの部分がどうも上手く理解できなくなるようです。
また、ダブルクォート内にある場合には改行コードはコメントにあるように\
でエスケープしないといけません。(LF=$'\\\x0A'
の最初の\\
の部分。)
一方、クォートしない状態だとそのまま解釈してくれて上手く行きます。
ただ、変更の前後で空白とか使いたい時もあってクォートしないと上手くいかなくなるので、 その場合には
$ echo "aa a\nbbb"|sed "s/a a\\\\n/"\\$'\n'"c c/"
a
c cbbb
こんな感じで、\\$'\n'
の部分の前後をそれぞれでクォートしてあげて
くっつけてあげればOK。
なので、基本的にこれで何でも出来ます。
シングルクォートの場合でも同じ様にその場だけ外してあげればOK。
$ echo "aaa\nbbb"|sed s/'\\n'/\\$'\n'/
aaa
bbb
上のページのコメントの例も\
を
おまけ
GNUとBSDのにはsedだけではなくて他にも色々と同じ名前のコマンドで 微妙に動作が違うものがあります。
ここでもおまけにしている所でsedの話をしてますが、
-i
オプションで元ファイルを置き換える時のバックアップ指定が
微妙に異なってバグを作ることがあります。
思い切って違う名前のコマンドとか、とか少なくともオプションが違うとかならまだあれですが、 同じような用途の同じ名前のオプションでこれだけ些細な違いは 流石にやめて欲しい。。。
おまけ2
ちなみに改行コードを\n
という表示に変換したいとき、
sedで直接やると難しいのでtr
を使ったりすることもありますが、
sentakuの中では下のようなawk
を使った方法に落ち着きました。
# Change line breaks to \n (to be shown), and remove the last line break
_s_show="$(echo "${_s_inputs[$n_input]}"|awk -F\n -v ORS="\\\\\\\\n" '{print}' |sed 's|\\\\n$||')"
追記: 2018/08/30
シェルの文字列変数置換操作でもっと簡単に出来ます。
_s_show=${_s_inputs[$n_input]//$'\n'/\\\\n}
_s_show=${_s_show//$'\n/\\\\n}
数多くループで回したりすると100倍近い違いが出ることもあるので、 なるべく外部コマンド使わずシェルの機能を使ったほうが良いです。
追記ここまで
-
入力側はダブルクォートで囲っている場合、Bashのエスケープで一度
\\
が\
に変換され、その後sedのエスケープでもう一回変換されて 最終的に\n
にするために\
が4つ必要です。この例だとそうですが、もし、他に変数等を使ってない場合には シングルクォートで囲って
$ echo "aaa\nbbb"|sed 's/\\n/\n/' aaa bbb
とすれば、今度は
\\
の部分は直接sedに渡されるため、 sedで一度エスケープされるだけで\n
になります。 ↩ -
ここでは変換後の方も改行を入れるときにはBash用エスケープが必要。
シングルクォートなら
echo "aaa\nbbb"|sed 's/\\n/\ /'
でOK。