シェルスクリプトはエンコーディングや行末に敏感ですか?

bash shell sh


MacでNW.jsのアプリを作っているのですが、アイコンをダブルクリックしてdevモードで実行したいと思っています。まずは、シェルスクリプトを動作させようとしています。

WindowsでVSCodeを使用して(時間を稼ぎたい)、プロジェクトのルートに run-nw ファイルを作成しました。

#!/bin/bash

cd "src"
npm install

cd ..
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &

が、このような出力が出てきます。

$ sh ./run-nw

: command not found  
: No such file or directory  
: command not found  
: No such file or directory  

Usage: npm <command>

where <command> is one of:  (snip commands list)

(snip npm help)

npm@3.10.3 /usr/local/lib/node_modules/npm  
: command not found  
: No such file or directory  
: command not found

本当に理解できません。

  • コマンドとして空行をとっているようです。エディター(VSCode)で、 \r\n\n に置き換えようとしました( \r が問題を引き起こす場合)が、何も変更されません。
  • フォルダーが見つからない( dirname 命令の有無にかかわらず)、または cd コマンドについて知らないようです。
  • npm への install 引数を理解していないようです
  • 本当に奇妙なのは、それでもアプリが実行されることです( npm install 手動でインストールした場合)...

正常に動作させることができず、ファイル自体に何か変なものがあるのではないかと思い、今回はvimを使ってMac上で直接新しいものを作成しました。全く同じ指示を入力して、そして...今では何の問題もなく動作しています。
2つのファイルの差分を見てみると、全く違いがないことがわかります。

違いは何が原因でできるのか?最初のスクリプトがうまくいかない原因は何ですか?どうやって調べればいいですか?

Update

受け入れられた回答の推奨に従い、間違った行末が戻った後、私は複数のことを確認しました。 ~/.gitconfig をWindowsマシンからコピーしたため、 autocrlf=true があったため、Windowsでbashファイルを変更するたびに、行末が \r\n に再設定されました。
なので、Dos2unix(macではHomebrewを使ってインストールする必要があります)を動かすことに加えて、Gitを使っている場合は設定を確認してみましょう。




Answer 1 Anthony Geoghegan


はい。Bashスクリプト、スクリプト自体と処理するデータの両方で、行末に敏感です。それらはUnixスタイルの行末を持つ必要があります。つまり、各行はラインフィード文字(10進数の10、ASCIIでは16進数の0A)で終了します。

スクリプト内のDOS/Windowsの行末

Windows や DOS スタイルの行末では、各行はキャリッジリターンとラインフィード文字で終了します。スクリプトファイルが Windows の行末で保存された場合、Bash はそのファイルを

#!/bin/bash^M
^M
cd "src"^M
npm install^M
^M
cd ..^M
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M

注:私はキャレット表記を使用して非表示文字を表しました。つまり、 ^M は復帰文字(他のコンテキストでは \r として表されます)を表すために使用されています。これは、 cat -v およびVimで使用されている手法と同じです。

この場合、復帰( ^M または \r )は空白として扱われません。bashは、シバンの後の最初の行(単一の復帰文字で構成される)を実行するコマンド/プログラムの名前として解釈します。

  • ^M という名前のコマンドがないため、次のように出力されます : command not found
  • "src"^M (または src^M )という名前のディレクトリがないため、次のように出力されます : No such file or directory
  • それは渡す install^M 代わりに、 install の引数として npm が発生 npm 文句を言います。

入力データのDOS/Windows行末

上記のように、キャリッジリターン付きの入力ファイルがある場合。

hello^M
world^M

その後、エディターや画面に書き込んだときは完全に正常に見えますが、ツールが奇妙な結果をもたらす可能性があります。たとえば、 grep は明らかにそこにある行を見つけることができません。

$ grep 'hello$' file.txt || grep -x "hello" file.txt
(no match because the line actually ends in ^M)

キャリッジリターンはカーソルを行の先頭に移動させるため、追加されたテキストはその行を上書きします。

$ sed -e 's/$/!/' file.txt
!ello
!orld

画面に書き込むと文字列が同じように見えるのに、文字列の比較に失敗するようです。

$ a="hello"; read b < file.txt
$ if [[ "$a" = "$b" ]]
  then echo "Variables are equal."
  else echo "Sorry, $a is not equal to $b"
  fi

Sorry, hello is not equal to hello

Solutions

解決策は、ファイルをUnixスタイルの行末を使用するように変換することです。これにはいくつかの方法があります。

  1. これは、 dos2unix プログラムを使用して実行できます。

    dos2unix filename
  2. でファイルを開きが可能なことが(再)保存する前に、次のコマンドを実行し、Vimのと、例えば、Unixの改行コードでファイルを保存するために、テキストエディタ(サブライム、メモ帳++ではなく、メモ帳)とのconfigureを:

    :set fileformat=unix
  3. -i または --in-place オプションをサポートするバージョンの sed ユーティリティ(例えば、GNU sed )を使用している場合は、次のコマンドを実行して、末尾の改行を削除できます。

    sed -i 's/\r$//' filename

    他のバージョンの sed では、出力リダイレクトを使用して新しいファイルに書き込むことができます。リダイレクトターゲットには必ず別のファイル名を使用してください(後で名前を変更できます)。

    sed 's/\r$//' filename > filename.unix
  4. 同様に、 tr 変換フィルターを使用して、入力から不要な文字を削除できます。

    tr -d '\r' <filename >filename.unix

サイグインバッシュ

CygwinのBashポートでは、 igncr を無視するように設定できるカスタムのigncrオプションがあります(おそらく、そのユーザーの多くがネイティブのWindowsプログラムを使用してテキストファイルを編集しているためです)。これは、 set -o igncr を実行して、現在のシェルで有効にできます。

このオプションの設定は現在のシェルプロセスにのみ適用されるため、無関係なキャリッジリターンを含むファイルを調達するときに役立ちます。DOS行末のシェルスクリプトが定期的に発生し、このオプションを永続的に設定したい場合は、 SHELLOPTS (すべて大文字)と呼ばれる環境変数を設定して、 igncr を含めることができます。この環境変数は、Bashの起動時(起動ファイルを読み取る前)にシェルオプションを設定するために使用されます。

便利なユーティリティ

file ユーティリティは、行末がテキストファイルで使用されている迅速に確認するのに便利です。ファイルの種類ごとに出力される内容は次のとおりです。

  • Unixの行末: Bourne-Again shell script, ASCII text executable
  • Macの行末: Bourne-Again shell script, ASCII text executable, with CR line terminators
  • DOS行末: Bourne-Again shell script, ASCII text executable, with CRLF line terminators

cat ユーティリティのGNUバージョンには、非印刷文字を表示する -v, --show-nonprinting オプションがあります。

dos2unix ユーティリティは、具体的のUnix、MacとDOS行末の間でテキストファイルを変換するために書かれています。

お役立ちリンク

ウィキペディアには、テキスト行の終わりをマークするさまざまな方法、そのようなエンコーディングの歴史、およびさまざまなオペレーティングシステム、プログラミング言語、インターネットプロトコル(FTPなど)での改行の扱い方をカバーする優れた記事があります。

古典的な Mac OS の行末を持つファイル

クラシックのMac OS(プリOS X)、各行は、キャリッジリターン(小数点以下13、ASCIIで進0D)で終了しました。スクリプトファイルがそのような行末で保存されている場合、Bashは次のように1つの長い行しか表示しません。

#!/bin/bash^M^Mcd "src"^Mnpm install^M^Mcd ..^M./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M

この単一の長い行はoctororpe( # )で始まるため、Bashは行(およびファイル全体)を単一のコメントとして扱います。

注:2001年に、アップルはBSD派生のNeXTSTEPオペレーティングシステムに基づいたMac OS Xを発売しました。その結果、OS XはUnixスタイルのLFのみの行末も使用するようになり、それ以降、CRで終了するテキストファイルは非常にまれになりました。それでも、Bashがそのようなファイルを解釈しようとする方法を示すことは価値があると思います。




Answer 2 CONvid19


JetBrains製品(PyCharm、PHPStorm、IDEAなど)では、 CRLF / LFclick オンにして、2種類の行区切り文字( \r\n\n )を切り替える必要があります。

enter image description here enter image description here




Answer 3 Igor Soudakevitch


不要なCR( '\ r')文字を取り除くもう1つの方法は、次のように tr コマンドを実行することです。

$ tr -d '\r' < dosScript.py > nixScript.py



Answer 4 tripleee


複製から来て、名前の最後に ^M が含まれるファイルがある場合は、次のようにして名前を変更できます。

for f in *$'\r'; do
    mv "$f" "${f%$'\r'}"
done

最初にこれらのファイルの名前が壊れている原因を適切に修正したいのですが(おそらく、ファイルを作成したスクリプトを dos2unix で編集してから再実行する必要がありますか?)、場合によってはこれが現実的ではありません。

$'\r' 構文はバッシュ固有です。別のシェルがある場合は、おそらく他の表記法を使用する必要があります。おそらく、shとbashの違いも参照してください




Answer 5 danR


MAC/Linuxで最も簡単な方法は、'touch'コマンドを使ってファイルを作成し、このファイルをVIまたはVIMエディタで開き、コードを貼り付けて保存します。これは自動的にウィンドウズ文字を削除します。