POSIX標準の考え方
Definition 1 3.195 Incomplete Line
- A sequence of one or more non-
<newline>characters at the end of the file.
Definition 2 3.206 Line
- A sequence of zero or more non-
<newline>characters plus a terminating<newline>character. - Lineとは,1つ以上の
<newline>文字以外の文字と,行末の<newline>文字によって成り立つ
多くのUNIX系のツールは Definition 2 に基づいており,改行文字で終わらない「行」は Definition 1 のように「行」とはみなされません.
Editorと改行
| エディタ | 末尾の改行の挙動 |
|---|---|
| VS Code | 設定次第で自動追加 (files.insertFinalNewline) |
| Vim | デフォルトで改行追加(:set nofixeol で抑制可) |
| Emacs | デフォルトで改行追加(require-final-newline 変数) |
catコマンドと結合
cat でファイルを結合する場合,改行で終わるファイル (a.txt と c.txt) と改行で終わらないファイル (b.txt) では,結合時の挙動が異なります.
まず,改行ありと改行なしの.txtファイルを生成します
改行ありファイルの生成
#!/bin/bash
words=(foo bar baz)
files=(a b c)
for i in "${!words[@]}"; do
echo "${words[i]}" > "${files[i]}.txt"
done#!/bin/zsh
words=(foo bar baz)
files=(a b c)
for i in {1..${#words[@]}}; do
echo "${words[i]}" > "${files[i]}.txt"
done改行なしファイルの生成
#!/bin/bash
words=(foo bar baz)
files=(a b c)
for i in "${!words[@]}"; do
printf "%s" "${words[i]}" > "${files[i]}_without_newline.txt"
done#!/bin/zsh
words=(foo bar baz)
files=(a b c)
for i in {1..${#words[@]}}; do
printf "%s" "${words[i]}" > "${files[i]}_without_newline.txt"
doneつぎに catコマンドで結合をしてみます.
cat コマンドで結合
% cat {a,b,c}.txt
foo
bar
baz% cat {a,b,c}_without_newline.txt
foobarbaz% wc commandと改行
Definition 3 wc コマンドマニュアル
- A line is defined as a string of characters delimited by a
<newline>character.
wcコマンドは <newline> の数で行数を数えています.実際に
## 改行なし
$ echo -n "Line not ending in a new line" | wc -l
0
## 改行あり
$ echo "Line ending with a new line" | wc -l
1Example 1 結合ファイルとwcコマンド
% cat {a,b,c}.txt | wc -l
3% cat {a,b,c}_without_newline.txt | wc -l
0git trackingテキストファイルを対象に改行有無判定スクリプト
Noteスクリプト全体
以下のシェルスクリプトは,バイナリファイルを除外した上で,ファイル末尾の改行の有無をチェックするスクリプトです
git ls-files -z | while IFS= read -r -d '' file; do
file --mime "${file}" | grep -q -e "charset=binary" -e "image/svg+xml" ||
tail -c1 "${file}" | read -r _ ||
echo "Missing newline: ${file}"
doneアルゴリズム
\begin{algorithm}
\caption{Checking Files for Missing Trailing Newlines}
\begin{algorithmic}
\State file\_list \(\leftarrow\) Get all Git-tracked files with NUL delimiter
\ForAll{file in file\_list}
\State mime\_info \(\leftarrow\) \texttt{file --mime file}
\If{mime\_info contains "charset=binary" OR "image/svg+xml"}
\State \textbf{continue (skip binary/SVG files)}
\Else
\State last\_byte \(\leftarrow\) Get final byte of file
\If{last\_byte is not newline character}
\State Output "Missing newline: file"
\EndIf
\EndIf
\EndFor
\end{algorithmic}
\end{algorithm}
各コマンド
| コマンド | 説明 |
|---|---|
git ls-files -z |
Gitで管理されているファイルを一覧表示-zオプションで区切り文字としてNUL文字を使用(ファイル名に特殊文字が含まれる場合の安全な処理のため) |
while IFS= read -r -d '' file |
NUL文字を区切りとしてファイルを順次処理IFS=で空白文字の保持-rでバックスラッシュの解釈を防止-d '':read でヌル文字区切りに対応 |
file --mime "${file}" | grep -q -e "charset=binary" -e "image/svg+xml" || |
バイナリファイルとSVGファイルをスキップ テキストファイルのみを処理 ||は「このコマンドが失敗した場合に次のコマンドを実行」を意味 |
tail -c1 "${file}" | read -r _ || |
ファイルの最後の1バイトをチェックreadは改行があれば0,なければ1を返す |
echo "Missing newline: ${file}" |
改行のないファイルを報告 |
Example 2 カレントディレクトリ以下のファイルに対しての改行判定スクリプト
#!/bin/bash
find . -maxdepth 1 -type f -print0 | while IFS= read -r -d '' file; do
file --mime "${file}" | grep -q -e "charset=binary" -e "image/svg+xml" ||
tail -c1 "${file}" | read -r _ ||
echo "Missing newline: ${file}"
done-print0: 各ファイル名の末尾に NUL文字 (\0) を付けて出力
改行なしと判定されたファイルに対して<newline>を付与する
Noteシェルスクリプト
find . -maxdepth 1 -type f -print0 | while IFS= read -r -d '' file; do
# バイナリファイルやSVGはスキップ
file --mime "$file" | grep -q -e "charset=binary" -e "image/svg+xml" && continue
# 最後の1バイトを確認(改行がないなら)
if ! tail -c1 "$file" | read -r _; then
echo >> "$file"; echo "✓ Newline added to: $file"
fi
doneecho >> "$file"で<newline>を行末に追加
改行忘れの対策
.editorconfigの用いた改行設定
EditorConfigとは?
- コードスタイル(インデントや改行コードなど)を統一するための設定ファイルの仕組み
- 異なるエディタ・OS間でのコードスタイルを統一させたいときに便利なツール
EditorConfigによる改行設定例
| プロパティ名 | 内容 | 例 |
|---|---|---|
indent_style |
インデントの種類(space または tab) |
indent_style = space |
indent_size |
インデントの幅 | indent_size = 4 |
end_of_line |
改行コード(lf/crlf/cr) |
end_of_line = lf |
charset |
文字コード | charset = utf-8 |
trim_trailing_whitespace |
行末の空白を削除するか | trim_trailing_whitespace = true |
insert_final_newline |
最終行の末尾に改行を追加するか | insert_final_newline = true |
# .editorconfig
# このファイルが設定のルートであることを示す
root = true
# すべてのファイルに適用
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = trueVSCodeでの改行設定
settings.jsonで以下の項目を追加します
{
// Editor Settings
"files.insertFinalNewline": true,
"notebook.insertFinalNewline": true
}| 設定キー | 意味 |
|---|---|
"files.insertFinalNewline": true |
ファイル保存時に、末尾に改行を自動で追加します(通常の .txt, .js, .py などすべて対象) |
"notebook.insertFinalNewline": true |
Jupyter Notebook(.ipynb)などのノートブック形式のファイルにおいて、セル内のソースに末尾改行を追加する設定(拡張機能依存) |
Appendix: 改行コードの種類
| 改行コード | 記号 | 名称 | 主なOS | バイナリ表現 | 説明 |
|---|---|---|---|---|---|
LF |
\n |
Line Feed(行送り) | Linux / macOS(Unix系) | 0x0A |
行末で「次の行の先頭」に移動 |
CRLF |
\r\n |
Carriage Return + Line Feed | Windows | 0x0D 0x0A |
行頭に戻りつつ次の行へ(古いタイプライタ由来) |
CR |
\r |
Carriage Return(復帰) | 古いMac OS(〜OS9) | 0x0D |
行頭に戻るだけ(現在はほぼ使われない) |
歴史的背景
タイプライターの動きと対応させると改行には3つの考え方がありました
LFは紙を上に移動させ (水平方向の位置は変わらない)CRは「キャリッジ」を戻して,次の入力文字が紙の左端の同じ行に表示CR+LFは両方を実行し,新しい行の入力準備