Windowsで作成したシェルスクリプトや設定ファイルをLinux上で実行した際、次のようなエラーが発生することがあります。
/bin/bash^M: bad interpreter: No such file or directory
また、シェルスクリプトの記述自体に問題がないにもかかわらず、コマンドが正しく実行されないこともあります。
この原因の一つが、WindowsとLinuxで使用される改行コードの違いです。
この記事では、次のsedコマンドを使って、Windows形式の改行コードをLinux形式に変換する方法を紹介します。
sed -i 's/\r$//' textfile
WindowsとLinuxの改行コードの違い
テキストファイルでは、行の終わりを表すために改行コードが使用されています。
WindowsとLinuxでは、一般的に次の改行コードが使われます。
| OS | 改行コード | 表記 |
|---|---|---|
| Windows | CRLF | \r\n |
| Linux | LF | \n |
Windowsでは、キャリッジリターンを表すCRと、ラインフィードを表すLFの2文字が使用されます。
一方、LinuxではLFのみが使用されます。
そのため、Windowsで作成したファイルをLinuxで使用すると、Linux側では不要な\rが行末に残ることがあります。
sedを使って改行コードを変換する
Windows形式の改行コードをLinux形式に変換するには、次のコマンドを実行します。
sed -i 's/\r$//' textfile
textfileの部分には、変換したいファイル名を指定します。
例えば、start.shというシェルスクリプトを変換する場合は、次のように実行します。
sed -i 's/\r$//' start.sh
このコマンドを実行すると、ファイル内の各行末にある\rが削除されます。
結果として、改行コードが次のように変換されます。
\r\n
↓
\n
コマンドの意味
コマンドを分解すると、次のような意味になります。
sed -i 's/\r$//' textfile
sed
sedは、テキストの置換や削除などを行うためのコマンドです。
「Stream Editor」の略で、ファイルや標準入力の内容を行単位で処理できます。
-i
-iは、ファイルを直接書き換えるためのオプションです。
-iを指定しない場合、変換結果は標準出力に表示されるだけで、元のファイルは変更されません。
例えば、変換結果だけを確認する場合は、次のように実行できます。
sed 's/\r$//' textfile
s/置換前/置換後/
sedのsコマンドは、文字列を置換するための構文です。
s/置換前の文字列/置換後の文字列/
今回のコマンドでは、次のようになっています。
s/\r$//
\r$に一致する文字を、空文字に置換しています。
つまり、該当する文字を削除しています。
\r
\rは、キャリッジリターンを表します。
Windows形式の改行コードCRLFに含まれるCRの部分です。
$
$は、行末を表す正規表現です。
そのため、次の指定は「行末にある\r」という意味になります。
\r$
行の途中にある\rではなく、行末に存在するものだけが削除されます。
なぜ\r\n$ではなく\r$でよいのか
Windowsの改行コードはCRLFであり、文字として表すと次の組み合わせになります。
\r\n
そのため、次のコマンドについて、
sed -i 's/\r$//' textfile
「Windowsの改行コードが\r\nなら、\r\n$と指定する必要があるのではないか」と疑問に思うかもしれません。
しかし、通常のsedでは\r$だけで行末のCRを指定できます。
これは、sedがファイルを1行ずつ読み込む際、行の区切りとなるLF、つまり\nを取り除いてから処理するためです。
例えば、Windows形式のファイルに次の1行が記録されているとします。
text\r\n
sedがこの行を読み込むと、行区切りとして使われている\nは処理対象に含まれず、パターンスペースには次の内容が格納されます。
text\r
この状態では、末尾に残っている文字は\rです。
そのため、次の正規表現で「行末にある\r」へ一致させられます。
\r$
ここで、$は改行文字そのものを表しているわけではありません。sedが現在処理している文字列の末尾を表しています。
処理の流れを整理すると、次のようになります。
ファイル上の内容
text\r\n
sedが読み込んだパターンスペース
text\r
\r$を空文字に置換
text
sedが出力時に改行を付加
text\n
結果として、Windows形式のCRLFがLinux形式のLFへ変換されます。
CRLF(\r\n)
↓
LF(\n)
つまり、ファイル上では改行コードが\r\nであっても、sedが処理する時点では末尾の\nが除かれているため、次の指定で問題ありません。
sed -i 's/\r$//' textfile
変換前に改行コードを確認する
ファイルの改行コードは、fileコマンドで確認できます。
file textfile
Windows形式の改行コードが含まれている場合、環境によっては次のように表示されます。
textfile: ASCII text, with CRLF line terminators
変換後に再度確認します。
file textfile
次のようにCRLFの表示がなくなっていれば、Linux形式に変換されています。
textfile: ASCII text
cat -vで\rを確認する
cat -vを使うと、通常は表示されない制御文字を確認できます。
cat -v textfile
Windows形式の改行コードが含まれている場合、行末に^Mが表示されることがあります。
#!/bin/bash^M
echo "Hello"^M
sedで変換します。
sed -i 's/\r$//' textfile
変換後に再度確認します。
cat -v textfile
#!/bin/bash
echo "Hello"
行末の^Mがなくなっていれば、変換できています。
元のファイルを残しておきたい場合
sed -iは元のファイルを直接書き換えます。
念のためバックアップを残したい場合は、-iの後ろに拡張子を指定します。
sed -i.bak 's/\r$//' textfile
このコマンドを実行すると、変換前のファイルが次の名前で保存されます。
textfile.bak
変換後に問題がなければ、バックアップファイルを削除できます。
rm textfile.bak
複数のファイルをまとめて変換する
カレントディレクトリ内にある複数のシェルスクリプトを変換する場合は、次のように指定できます。
sed -i 's/\r$//' *.sh
サブディレクトリを含めて変換する場合は、findコマンドと組み合わせる方法があります。
find . -type f -name '*.sh' -exec sed -i 's/\r$//' {} +
このコマンドでは、現在のディレクトリ以下にある.shファイルを検索し、対象ファイルに対してsedを実行します。
ただし、多数のファイルを一括変更する場合は、事前にGitへコミットするか、バックアップを作成しておくと安全です。
dos2unixを使用する方法
改行コードを変換する専用コマンドとして、dos2unixもあります。
dos2unix textfile
環境にインストールされていない場合は、UbuntuやDebian系のLinuxでは次のコマンドでインストールできます。
sudo apt install dos2unix
dos2unixは目的が分かりやすく、改行コードを変換するだけであれば便利です。
一方、sedは多くのLinux環境に標準で入っているため、追加のパッケージをインストールできない環境でも使用しやすいという利点があります。
Gitで改行コードを管理する
Gitを利用している場合、改行コードの違いが繰り返し発生しないように、.gitattributesを設定する方法もあります。
例えば、シェルスクリプトを常にLFで管理する場合は、次のように記述します。
*.sh text eol=lf
すべてのテキストファイルをLFに統一する場合は、次のように設定できます。
* text=auto eol=lf
ただし、プロジェクト内にWindows専用のバッチファイルなどが含まれている場合は、ファイルの種類ごとに設定した方が安全です。
*.sh text eol=lf
*.bat text eol=crlf
まとめ
Windows形式の改行コードをLinux形式に変換するには、次のコマンドを使用できます。
sed -i 's/\r$//' textfile
このコマンドでは、各行末にある\rを削除することで、改行コードをCRLFからLFへ変換しています。
変換前に結果を確認したい場合は、-iを付けずに実行します。
sed 's/\r$//' textfile
元のファイルを残したい場合は、バックアップ用の拡張子を指定します。
sed -i.bak 's/\r$//' textfile
シェルスクリプトで^Mやbad interpreterなどのエラーが発生した場合は、ファイルの内容だけでなく、改行コードも確認してみるとよいでしょう。