ファイルの最後に改行を追加する方法は?


190

バージョン管理システムを使用すると、diffが言うときにノイズに悩まされますNo newline at end of file

だから私は疑問に思っていた:それらのメッセージを取り除くためにファイルの最後に改行を追加する方法?



1
以下のすべてのファイルを再帰的にサニタイズする優れたソリューション。@Patrick Oscityによる回答
Qwerty


今後、テキストエディタには多くの場合、あなたと共同編集者がクリーンに保つために使用できる末尾の改行を確保するオプションがあります。
ニックT

回答:


44

プロジェクトを再帰的にサニタイズするには、このonelinerを使用します。

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done

説明:

  • git ls-files -zリポジトリ内のファイルをリストします。操作を特定のファイル/ディレクトリに制限したい場合に役立つかもしれない追加のパラメータとして、オプションのパターンを取ります。別の方法として、find -print0 ...影響を受けるファイルをリストするために、または同様のプログラムを使用することができます-必ずNUL-delimitedエントリを発行するようにしてください。

  • while IFS= read -rd '' f; do ... done 空白や改行を含むファイル名を安全に処理して、エントリを反復処理します。

  • tail -c1 < "$f" ファイルから最後の文字を読み取ります。

  • read -r _ 末尾の改行がない場合、ゼロ以外の終了ステータスで終了します。

  • || echo >> "$f" 前のコマンドの終了ステータスがゼロ以外の場合、ファイルに改行を追加します。


また、あなたはファイルのサブセットをサニタイズだけしたい場合は、このようにそれを行うことができます:find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Lundbergのパー

@StéphaneChazelasの良い提案は、これを私の答えに取り入れようとします。
パトリックオスシティ

@PerLundbergではgit ls-files、バージョン管理で追跡されないファイルを編集することからあなたを救うパターンを渡すこともできます。
パトリックオスシティ

@StéphaneChazelasを追加しIFS= てセパレータを設定解除すると、周囲の空白を保持するのに適しています。nullで終了するエントリは、名前に改行が含まれるファイルまたはディレクトリがある場合にのみ関連します。改行はかなりフェッチされているように見えますが、一般的なケースを処理するより適切な方法です、私は同意します。小さな警告として:-dオプションreadはPOSIX shでは利用できません。
パトリックオスシティ

はい、それゆえ私のzsh / bashのです。tail -n1 < "$f"で始まるファイル名の問題を回避するための私の使用も参照してください-tail -n1 -- "$f"と呼ばれるファイルでは機能しません-)。答えがzsh / bash固有になったことを明確にしたいかもしれません。
ステファンChazelas

203

どうぞ

sed -i -e '$a\' file

また、OS Xの場合sed

sed -i '' -e '$a\' file

これは\n、ファイルが改行で終了していない場合にのみ、ファイルの最後に追加されます。したがって、2回実行すると、別の改行は追加されません。

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0

1
@jwd:From man sed$ Match the last line.しかし、たぶん偶然にしか機能しないかもしれません。ソリューションも機能します。
l0b0

1
あなたのソリューションもよりエレガントであり、私はそれをテストしてコミットしましたが、どのように機能しますか?$最後の行に一致する場合、なぜ改行が既に含まれている文字列に別の改行を追加しないのですか?
l0b0

27
には2つの異なる意味があり$ます。formなどの正規表現内/<regex>/では、通常の「行末に一致」という意味があります。それ以外の場合、アドレスとして使用されるsedは、特別な「ファイルの最終行」の意味を与えます。デフォルトではsedが出力に改行を追加します(まだ存在しない場合)。コード「$ a \」は、「ファイルの最後の行に一致し、何も追加しない」というだけです。しかし、暗黙的に、sedは、処理していないすべての行(この$行など)に改行を追加します(まだない場合)。
jwd

1
マンページについて:あなたが言及している引用は「アドレス」セクションの下にあります。内部に配置/regex/すると、別の意味が与えられます。FreeBSDのマンページはもう少し参考になると思います:freebsd.org/cgi/man.cgi
query=

2
ファイルがすでに改行で終わっている場合、これは変更しませんが、書き換えてタイムスタンプを更新します。それは重要かもしれないし、そうでないかもしれない。
キーストンプソン

39

ご覧ください:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

そのecho "" >> noeol-fileトリックを行う必要があります。(または、これらのファイルの識別修正を依頼するつもりでしたか?)

編集""から削除echo "" >> foo(@yuyichaoのコメントを参照) edit2""再度追加(ただし @Keith Thompsonのコメントを参照)


4
これ""は(少なくともbashには)必要ではなくtail -1 | wc -l、最後に改行なしでファイルを見つけるために使用できます
-yuyichao

5
@yuyichao:""bashには必要echoありませんが、引数なしで呼び出されたときに何も表示されない実装を見てきました(ただし、これを実行できるものはありません)。 echo "" >> noeol-fileおそらくわずかに堅牢です。 printf "\n" >> noeol-fileさらにそうです。
キーストンプソン

2
@KeithThompson、csh's echoは、引数が渡されない場合は何も出力しないことがわかっています。私たちは非ボーンのようなシェルをサポートするつもりならしかし、その後、我々はそれが作るべきecho ''代わりのecho ""ようにecho ""出力リレーう""<newline>rcか、es例えば。
ステファンシャゼラス

1
@StéphaneChazelas:そしてtcsh、とは異なりcsh、引数なしで呼び出された場合、-の設定に関係なく、改行を出力します$echo_style
キーストンプソン

16

を使用する別のソリューションed。このソリューションは、最後の行にのみ影響し、\n欠落している場合のみ:

ed -s file <<< w

基本的には、スクリプトを介して編集用にファイルを開き、スクリプトはwファイルをディスクに書き戻す単一のコマンドです。これは、ed(1)manページにある次の文に基づいています。

制限事項
       (...)

       テキスト(非バイナリ)ファイルが改行文字で終了していない場合、
       edは、読み取り/書き込み時に1を追加します。バイナリの場合
       ファイル、edは読み取り/書き込み時に改行を追加しません。

1
これは私に改行を追加しません。
-Olhovsky

4
私のために働く; 「改行が追加されました」(Arch Linuxではed-1.10-1)も印刷されます。
ステファンマジェフスキー

12

存在しない最終的な改行をテキストファイルに追加するための、POSIX準拠のシンプルでポータブルな方法は次のとおりです。

[ -n "$(tail -c1 file)" ] && echo >> file

この方法では、ファイル全体を読み取る必要はありません。単にEOFをシークし、そこから動作することができます。

このアプローチでは、背中の後ろに一時ファイルを作成する必要もありません(たとえば、sed -i)。そのため、ハードリンクは影響を受けません。

echoは、コマンド置換の結果が空でない文字列である場合にのみ、ファイルに改行を追加します。これは、ファイルが空でなく、最後のバイトが改行でない場合にのみ発生することに注意してください。

ファイルの最後のバイトが改行の場合、tailはそれを返し、コマンド置換はそれを取り除きます。結果は空の文字列です。-nテストは失敗し、エコーは実行されません。

ファイルが空の場合、コマンド置換の結果も空の文字列になり、再びエコーは実行されません。空のファイルは無効なテキストファイルではなく、空の行を持つ空でないテキストファイルと同等ではないため、これは望ましいことです。


1
yashファイルの最後の文字がマルチバイト文字(たとえば、UTF-8ロケール)の場合、またはロケールがCで、ファイルの最後のバイトに8番目のビットが設定されている場合は機能しないことに注意してください。他のシェル(zshを除く)では、ファイルがNULバイトで終了した場合、改行は追加されません(ただし、これは、改行が追加された後でも入力が非テキストになることを意味します)。
ステファンシャゼル


1
フォルダーおよびサブフォルダー内のすべてのファイルに対してこれを実行することは可能ですか?
-Qwerty

12

関係なく改行を追加します。

echo >> filename

Pythonを使用して、改行を追加する前に改行が最後に存在するかどうかを確認する方法を次に示します。

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f

1
pythonの起動時間が遅いため、どのようなループでもpythonバージョンを使用しません。もちろん、必要に応じてPythonでループを実行できます。
ケビンコックス

2
Pythonの起動時間はここでは0.03秒です。あなたは本当に問題があると思いますか?
アレクサンダー

3
ループでpythonを呼び出す場合、起動時間は重要です。そのため pythonでループ実行することを検討するように言ったのです。 その後、起動コストは一度だけ発生します。私にとって、スタートアップの費用の半分はスニピット全体の時間の半分以上であり、その大きなオーバーヘッドを考慮したいと思います。(繰り返しますが、少数のファイルのみを行う場合は無関係です)
ケビンコックス

2
echo ""は、より堅牢なようですecho -n '\n'。または使用することができますprintf '\n'
キーストンプソン

2
これは私にとってはうまくいきました
ダニエル・ゴメス・リコ

8

最速のソリューションは次のとおりです。

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. 本当に速いです。
    中サイズのファイルでは、seq 99999999 >fileこれにはミリ秒かかります。
    他のソリューションには時間がかかります。

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
  2. ash、bash、lksh、mksh、ksh93、attsh、およびzshで機能しますが、yashでは機能しません。

  3. 改行を追加する必要がない場合、ファイルのタイムスタンプを変更しません。
    ここで紹介する他のすべてのソリューションは、ファイルのタイムスタンプを変更します。
  4. 上記のソリューションはすべて有効なPOSIXです。

yash(および上記のすべてのシェル)に移植可能なソリューションが必要な場合、もう少し複雑になる可能性があります。

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi

7

ファイルの最後のバイトが改行かどうかをテストする最も速い方法は、その最後のバイトのみを読み取ることです。それはでできましたtail -c1 file。ただし、ファイルの最後の文字がUTF-である場合、コマンド展開内のシェルの通常の末尾の改行の削除に応じて、バイト値が改行かどうかをテストする単純な方法は(たとえば)yashで失敗します8値。

ファイルの最後のバイトが新しい行であるかどうかを確認する、正しいPOSIX準拠のすべての(合理的な)シェルの方法は、xxdまたはhexdumpを使用することです。

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

次に、上記の出力を比較して0A、堅牢なテストを提供します。
それ以外の場合は空のファイルに新しい行を追加しないようにしてください。もちろん
、最後の文字の提供に失敗するファイル0A

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

短くて甘い。これは最後のバイトを読み取るだけなので、非常に短い時間で完了します(EOFにシークします)。ファイルが大きいかどうかは関係ありません。次に、必要に応じて1バイトのみを追加します。

一時ファイルは不要であり、使用されません。ハードリンクは影響を受けません。

このテストを2回実行すると、別の改行追加されません


1
@crw有用な情報が追加されると思います。
-sorontar

2
xxdまたhexdump、POSIXユーティリティでもないことに注意してください。POSIXツールチェストでod -An -tx1は、バイトの16進数値を取得する必要があります。
ステファンシャゼル

@StéphaneChazelasそれを回答として投稿してください。私はこのコメントを何度も探してここに来ました:)
kelvin

@kelvin、私が更新した私の答えを
ステファンChazelas

POSIXはLFの値が0x0aであることを保証しないことに注意してください。最近では非常にまれですが、POSIXシステムはまだありません(EBCDICベースのシステム)。
ステファンシャゼラス

4

ファイルを最後に編集したユーザーのエディターを修正することをお勧めします。あなたがファイルを編集した最後の人である場合-あなたはどのエディタを使用していますか、私はテキストメイトを推測しています..?


2
Vimは問題のエディターです。しかし、一般的に、あなたは正しいです、私は記号を修正するだけではありません;)
k0pernikus

6
vimの場合、vimにファイルの最後に新しい行を追加しないようにするには、邪魔にならないようにしてバイナリファイル保存のダンスを実行する必要があります。そのダンスはしないでください。または、単に既存のファイルを修正するには、vimでそれらを開き、ファイルを保存します
。vim

3
マイemacsファイルの末尾に改行を追加しないでください。
enzotib

2
コメント@ AD7sixのおかげで、物事をコミットするときに、元のファイルに最後に改行がないことについて、差分から幻のレポートを取得し続けます。どのようにvimでファイルを編集しても、そこに改行を入れないようにすることはできません。ですから、vimがそれをやっているだけです。
スティーブンルー

1
@enzotib:私が持っ(setq require-final-newline 'ask)ている.emacs
キーストンプソン

3

パイプラインを処理するときに、すぐに改行を追加するだけの場合は、これを使用します。

outputting_program | { cat ; echo ; }

POSIXにも準拠しています。

その後、もちろん、ファイルにリダイレクトできます。


2
パイプラインでこれを使用できるという事実は役に立ちます。これにより、ヘッダーを除くCSVファイルの行数をカウントできます。また、改行またはキャリッジリターンで終わらないWindowsファイルの正確な行数を取得するのに役立ちます。 cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
カイルトール

3

入力にヌルがない場合:

paste - <>infile >&0

... infileがまだない場合は、常にinfileの末尾にのみ改行を追加すれば十分です。そして、入力ファイルを一度だけ読むだけで正しくなります。


stdinとstdoutは同じオープンファイル記述を共有するため(ファイル内のカーソル)、このようには機能しません。paste infile 1<> infile代わりに必要になります。
ステファンシャゼラス

2

質問に直接回答するわけではありませんが、改行で終わらないファイルを検出するために作成した関連スクリプトを次に示します。とても速いです。

find . -type f | # sort |        # sort file names if you like
/usr/bin/perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

perlスクリプトは(オプションでソートされた)ファイル名のリストをstdinから読み取り、ファイルごとに最後のバイトを読み取って、ファイルが改行で終わるかどうかを判別します。各ファイルの内容全体を読み取らないため、非常に高速です。読み込むファイルごとに1行を出力し、何らかのエラーが発生した場合は「error:」、ファイルが空の場合は「empty:」(改行で終わらない!)、「EOL:」(「ファイルが改行で終わる場合は「line」)、ファイルが改行で終わらない場合は「no EOL:」

注:スクリプトは、改行を含むファイル名を処理しません。GNUまたはBSDシステムを使用している場合、次のように、-print0を追加して検索、-zを並べ替え、-0をperlに追加することで、可能なすべてのファイル名を処理できます。

find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

もちろん、出力に改行を含むファイル名をエンコードする方法を考え出す必要があります(読者のための演習として残されています)。

必要に応じて、出力をフィルター処理して、改行を持たないファイルに改行を追加することができます。

 echo >> "$filename"

シェルやその他のユーティリティの一部のバージョンでは、そのようなファイルを読み取るときに、不足している最終改行を適切に処理できないため、最終改行がないとスクリプトにバグが生じる可能性があります。

私の経験では、最終的な改行がないのは、さまざまなWindowsユーティリティを使用してファイルを編集したことが原因です。vimがファイルの編集時に最終改行の欠落を引き起こすことはありませんが、そのようなファイルについて報告します。

最後に、ファイル名の入力をループして改行で終わらないファイルを印刷できる、はるかに短い(ただし遅い)スクリプトがあります。

/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...

1

vi/ vim/ exエディタは自動的に追加し<EOL>たファイルはすでにそれを持っていない限り、EOFで。

次のいずれかを試してください:

vi -ecwq foo.txt

次と同等です:

ex -cwq foo.txt

テスト:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt

複数のファイルを修正するには、次を確認してください:多くのファイルの「ファイルの終わりに改行なし」を修正する方法?SOで

なぜこれがとても重要なのですか?ファイルをPOSIX互換に保つため。


0

受け入れられた回答を現在のディレクトリ(およびサブディレクトリ)内のすべてのファイルに適用するには:

$ find . -type f -exec sed -i -e '$a\' {} \;

これはLinux(Ubuntu)で機能します。OS Xでは、おそらく-i ''(未テスト)を使用する必要があります。


4
find .ファイルを含むすべてのファイルがリストされていることに注意してください.git。除外するには:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
friederbluemle

私がそれを実行する前に、私はそれについてこのコメント/考えを読んでいたらいいのですが。しかたがない。
kstev

0

少なくともGNUバージョンでは、入力を単純grep ''またはawk 1正規化し、まだ存在しない場合は最終的な改行を追加します。彼らはプロセスでファイルをコピーしますが、これは大きければ時間がかかりますが(ソースは大きすぎて読まないはずですか?)、あなたが何かをしない限りmodtimeを更新します

 mv file old; grep '' <old >file; touch -r old file

(ただし、チェックインしているファイルについては、変更したため大丈夫かもしれませんが)さらに注意しない限り、ハードリンク、デフォルト以外のパーミッション、ACLなどを失います。


またはgrep '' file 1<> file、それでもファイルを完全に読み書きしますが。
ステファンシャゼラス

-1

これはAIX kshで機能します。

lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
    echo "/n" >> *filename*
fi

私の場合、ファイルに改行がない場合、wcコマンドは値を返し2、改行を書き込みます。


フィードバックは、賛成票または反対票の形で提供されます。または、コメントで回答/質問をより詳しく説明するように求められますが、回答の本文でそれを求めることは意味がありません。要点を維持し、stackexchangeへようこそ!
k0pernikus

-1

Patrick Oscityの回答に追加するだけで、特定のディレクトリに適用したいだけなら、以下を使用することもできます。

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

改行を追加するディレクトリ内でこれを実行します。


-1

echo $'' >> <FILE_NAME> ファイルの最後に空白行を追加します。

echo $'\n\n' >> <FILE_NAME> ファイルの最後に3つの空白行を追加します。


StackExchangeにはおもしろいフォーマットがあります。私はあなたのためにそれを修正しました:
peterh

-1

ファイルがWindowsの行末で終了し\r\nていて、Linuxを使用している場合は、このsedコマンドを使用できます。\r\n最後の行にまだ追加されていない場合にのみ追加されます。

sed -i -e '$s/\([^\r]\)$/\1\r\n/'

説明:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\1\r\n    replace it with itself and add \r\n

最後の行にすでにが含まれている\r\n場合、検索正規表現は一致しないため、何も起こりません。


-1

次のfix-non-delimited-lineようなスクリプトを書くことができます。

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  if sysopen -rwu0 -- "$file"; then
    if sysseek -w end -1; then
      read -r x || print -u0
    else
      syserror -p "Can't seek in $file before the last byte: "
      ret=1
    fi
  else
    ret=1
  fi
done
exit $ret

ここで与えられたソリューションのいくつかに反して、それ

  • プロセスをフォークせず、ファイルごとに1バイトだけを読み取り、ファイルを書き換えない(改行を追加するだけ)という点で効率的である必要があります。
  • シンボリックリンク/ハードリンクを壊したり、メタデータに影響を与えたりしません(また、ctime / mtimeは、改行が追加されたときにのみ更新されます)
  • 最後のバイトがNULであるか、マルチバイト文字の一部である場合でも、正常に動作するはずです。
  • ファイル名に含まれる文字または非文字に関係なく、正常に動作するはずです
  • 読み取り不可、書き込み不可、またはシーク不可能なファイルを正しく処理する必要があります(それに応じてエラーを報告します)。
  • 空のファイルに改行を追加しないでください(ただし、その場合は無効なシークに関するエラーが報告されます)

たとえば、次のように使用できます。

that-script *.txt

または:

git ls-files -z | xargs -0 that-script

POSIXly、機能的に同等の何かをすることができます

export LC_ALL=C
ret=0
for file do
  [ -s "$file" ] || continue
  {
    c=$(tail -c 1 | od -An -vtc)
    case $c in
      (*'\n'*) ;;
      (*[![:space:]]*) printf '\n' >&0 || ret=$?;;
      (*) ret=1;; # tail likely failed
    esac
  } 0<> "$file" || ret=$? # record failure to open
done
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.