ShellScript: テキストファイル末尾の改行を削除する.


要約

テキストファイル末尾の改行を削除する代表的な方法は以下の3つ.

echo -n
 利点:簡潔.
 難点:文末に空行が複数連続してある場合,それらが自動で削除される.
head -c -1
 利点:最も簡潔.空行があっても削除されない.
 難点:Macのに元から入っているheadコマンドではマイナス値を指定できない.
sed ${/^$/d;}
 利点:文末が空行のときに,その行を削除.
 難点:2つの異なる意味のダラーがでてきて意味がわかりづらい.echo -nのように行末の改行を削除することはできない.

はじめに

 テキストファイルの最終行の末尾が改行で終わる場合もあるし,改行で終わらない場合もある.そして,テキストファイル末尾に改行を追加もしくは削除したいことがある[1][2][3][10].「テキストファイル末尾の改行」とは全く異なる二つの状況がある.すなわち,末尾の改行が文字列の末尾に付いてPOSIX定義の「行」をなす場合と,改行のみ(いわゆる空行)のときである.それら2通りへの対応が共通することもあるし,異なることもある.わかりづらいのでまとめることにした.

環境

MacOS: 10.15.3
zsh: 5.7.1

準備

  • 最終行を改行で終えるファイル(file_1.txt)(file_2.txt)を準備する.
  • cat -eとすると改行がダラー$で見えるようになる.

% cat -e file_1.txt
a$
b$
c$
最終行が空行

% cat -e file_2.txt
A$
B$
C$
$
$

echo -nを使う方法[4]

  • echoは自動で改行が加わる.逆に-nオプションをつけると改行しなくなり.改行があるものは自動的に改行が削除される.それを利用する.
  • catでファイル内容を出力し,それをコマンド置換$()し,変数tmpにいれる.
  • echo -nの内容をcat -eで受けて改行を確認する.
file_1.txt,"c"の後ろの改行"$"が無くなっている.

% tmp=$(cat file_1.txt) ; echo -n $tmp | cat -e
a$
b$
c%  #zshでは改行がない行は,色が反転した"%"が末尾につく.
file_2.txt,空行が全て削除されている.
% tmp=$(cat file_2.txt) ; echo -n $tmp | cat -e
A$
B$
C%  #改行が削除された.

head -c -1を使う方法[10].

  • 簡潔でよい.
  • 空行も削除されない.
  • ただし,Macのデフォルトではできない[5].
  • 下記で,Macのheadであえて冗長にやってみた.
  1. catでファイル内容を出力
  2. それをwcで受ける.行数,単語数,バイト数の順に表示され,今回はバイト数を利用する.
  3. 上記の結果をawkで受け,3つめのフィールド(バイト数)を取り出す.
  4. それをコマンド置換$()で変数のように扱えるようにする.
  5. 上記の結果から-1する.この演算を$(())でまとめる.
  6. 上記結果を変数x に代入.このxは一文字目からファイル最後の改行の直前の文字までのバイト数.
  7. あらためてcatでファイル内容を出力(下記コードの2行目).
  8. head -c $xでで1バイト目からxバイト目まで取り出す.

x=$(($(cat file_1.txt | wc | awk '{print $3}') - 1)) \
; cat file_2.txt \
| head -c $x

sedを用いる方法.

 最終行が空行の場合,その行を削除すれば,すなわちテキストファイル末尾の改行を削除することになる.

sed '$d'[6]

  • 最後の行が空行でなくとも削除してしまうのが難点.
  • $はsedの文法内で最後の行を示すアドレッシング記号であり,正規表現の$とは異なるものであることに注意する[7].最後の行($)を削除(d)するという意味.

  • sed -n '$!p' file_1.txtでも可.最終行以外(!)をプリント(p)するという意味.

確認

% sed '$d' file_1.txt | cat -e
a$
b$   #c$の行が削除されている.
確認
% sed '$d' file_2.txt | cat -e 
A$
B$
C$
$   #最後の空行が削除された.

sed ${/^$/d;}[8]

  • 最終行が空行のときのみ削除する.
  • 1つめの$はsedの文法の最終行を表すアドレッシング記号.2つめの$は文末を表す正規表現であることに注意.
  • カーリーブレイス{}内のコマンドをセミコロンで区切ると1行で書てもエラーがでない[9].
  • ^$は行頭^と行末$に文字がないことを正規表現している.それをスラッシュで挟む/^$/とsed文法内でアドレス,すなわち正規表現で示した条件を満たした行を示す[7].その行を削除dする.その操作をsed文法でのアドレッシング記号としての$で示された最終行で行う.

  • 注)sedの文法ではカーリーブレイスは,その中に複数のコマンドを改行して縦に並べると,それらを順次実行してくれるという機能.そのコマンド群をセミコロンで区切れば改行せずに一行でかける.

考察,まとめ

 テキストファイル末尾の改行を削除することはあまりないかもしれないが,改行を末尾に追加する方法と併せて知っておくとよいと考え,まとめた.


参考

[1]https://zariganitosh.hatenablog.jp/entry/20131216/end_of_file_lf_control
ファイル末尾の改行を自在にコントロールする - ザリガニが見ていた...。
[2]https://unix.stackexchange.com/questions/254644/how-do-i-remove-newline-character-at-the-end-of-file
shell script - How do I remove newline character at the end of file? - Unix & Linux Stack Exchange
[3]https://stackoverflow.com/questions/15520339/how-to-remove-carriage-return-and-newline-from-a-variable-in-shell-script
unix - How to remove carriage return and newline from a variable in shell script - Stack Overflow
[4]https://qiita.com/euxn23/items/f7c60999a121f906ccdb
shell の出力の末尾の改行を取り除く - Qiita
[5]http://geotommy01.seesaa.net/article/459317291.html
headのmacOSでの挙動について: ftommy's blog
[6]https://qiita.com/richmikan@github/items/4317efffdfd57dc24cf7
テキストの最後の行だけ消したい時、どうやればいい? - Qiita
[7]Dale Dougherty,Arnold Robbins,1997年,福崎俊博訳,"sed & awk プログラミング 改訂版",オライリージャパン,pp.66
[8]https://orebibou.com/2016/06/sed%E3%81%A7%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%9C%80%E7%B5%82%E8%A1%8C%E3%81%8C%E7%A9%BA%E8%A1%8C%E3%81%A0%E3%81%A3%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AE%E3%81%BF%E3%80%81%E3%81%9D/
sedでファイルの最終行が空行だった場合のみ、その行を削除する | 俺的備忘録 〜なんかいろいろ〜
[9]Dale Dougherty,Arnold Robbins,1997年,福崎俊博訳,"sed & awk プログラミング 改訂版",オライリージャパン,pp.88
[10]https://qiita.com/kkdd/items/35707fa38358ec8c17fc
テキストデータ末尾の改行 \n を削除(および追加、カウント) - Qiita