MSYS2 と同様に、Linux でも CRLF (キャリッジ リターン) を含む Bash スクリプトを処理できますか?

MSYS2 と同様に、Linux でも CRLF (キャリッジ リターン) を含む Bash スクリプトを処理できますか?

次のような簡単なスクリプトがあるとしますtmp.sh

echo "testing"
stat .
echo "testing again"

些細なことですが、\r\n行末には (つまり、CRLF、つまり復帰+改行) があります。Web ページでは行末が保持されないため、16 進ダンプを次に示します。

$ hexdump -C tmp.sh 
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0d 0a  |echo "testing"..|
00000010  73 74 61 74 20 2e 0d 0a  65 63 68 6f 20 22 74 65  |stat ...echo "te|
00000020  73 74 69 6e 67 20 61 67  61 69 6e 22 0d 0a        |sting again"..|
0000002e

スクリプトは Windows の MSYS2 で開始および開発されたため、CRLF 行末になっています。そのため、Windows 10 の MSYS2 で実行すると、予想どおりの結果になります。

$ bash tmp.sh
testing
  File: .
  Size: 0               Blocks: 40         IO Block: 65536  directory
Device: 8e8b98b6h/2391513270d   Inode: 281474976761067  Links: 1
Access: (0755/drwxr-xr-x)  Uid: (197609/      USER)   Gid: (197121/    None)
Access: 2020-04-03 10:42:53.210292000 +0200
Modify: 2020-04-03 10:42:53.210292000 +0200
Change: 2020-04-03 10:42:53.210292000 +0200
 Birth: 2019-02-07 13:22:11.496069300 +0100
testing again

ただし、このスクリプトを Ubuntu 18.04 マシンにコピーして実行すると、次のようになります。

$ bash tmp.sh
testing
stat: cannot stat '.'$'\r': No such file or directory
testing again

同じ行末を持つ他のスクリプトでも、Ubuntu bash で次のエラーが発生しました。

line 6: $'\r': command not found

...おそらく空行からでしょう。

つまり、Ubuntuの何かがキャリッジリターンで詰まっているのは明らかです。BASH とキャリッジリターンの動作:

Bashとは関係ありません: \rと\nはBashではなくターミナルによって解釈されます

... ただし、これはコマンド ラインにそのまま入力されたものにのみ当てはまると思います。ここでは、\rおよびが\nすでにスクリプト自体に入力されているため、Bash はここで を解釈するはずです\r

Ubuntu の Bash のバージョンは次のとおりです。

$ bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

...そして、MSYS2 の Bash のバージョンは次のとおりです。

$ bash --version
GNU bash, version 4.4.23(2)-release (x86_64-pc-msys)

(それほど違いはないようですが…)

とにかく、私の質問は、Ubuntu/Linux 上の Bash に、 を\r(いわば)「印刷可能な文字」(この場合、有効なコマンドの一部である可能性のある文字を意味し、bash はそのように解釈します) として解釈するのではなく、 を無視するように説得する方法があるかどうかです。編集:それなしスクリプト自体を変換する必要がある (git などでそのようにチェックされている場合、CRLF 行末で同じままになります)

編集2: 私としては、この方法の方が良いと思います。なぜなら、一緒に作業している他の人が Windows のテキスト エディターでスクリプトを再度開き、\r\nスクリプトに再度導入してコミットする可能性があるためです。そうすると、コミットが際限なく続くことになり、リポジトリを汚染すること\r\nになるかもしれません。\n

編集2: コメントで @Kusalananda がdos2unix( sudo apt install dos2unix) と言及しました。これを書いているだけであることに注意してください:

$ dos2unix tmp.sh 
dos2unix: converting file tmp.sh to Unix format...

... はファイルをその場で変換します。stdout に出力するには、stdin リダイレクトを設定する必要があります。

$ dos2unix <tmp.sh | hexdump -C
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0a 73  |echo "testing".s|
00000010  74 61 74 20 2e 0a 65 63  68 6f 20 22 74 65 73 74  |tat ..echo "test|
00000020  69 6e 67 20 61 67 61 69  6e 22 0a                 |ing again".|
0000002b

...そして、原理的には、これを Ubuntu で実行することができ、この場合は動作するようです。

$ dos2unix <tmp.sh | bash
testing
  File: .
  Size: 20480       Blocks: 40         IO Block: 4096   directory
Device: 816h/2070d  Inode: 1572865     Links: 27
Access: (1777/drwxrwxrwt)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-04-03 11:11:00.309160050 +0200
Modify: 2020-04-03 11:10:58.349139481 +0200
Change: 2020-04-03 11:10:58.349139481 +0200
 Birth: -
testing again

しかし、覚えるのが少し面倒なコマンドであることに加えて、stdinがターミナルではなくなるため、bashのセマンティクスも変わります。この些細な例ではうまくいったかもしれませんが、例えばhttps://stackoverflow.com/questions/23257247/pipe-a-script-into-bashたとえば、より大きな問題の場合などです。

答え1

私の知る限り、Bash に Windows スタイルの行末を受け入れるように指示する方法はありません。

Windowsの場合、コミット時に設定フラグを使用して行末を自動的に変換するGitの機能に頼るのが一般的ですautocrlf。たとえば、GitHub の行末に関するドキュメントこれは GitHub に固有のものではありません。この方法では、ファイルは Unix スタイルの行末でリポジトリにコミットされ、各クライアント プラットフォームに合わせて適切に変換されます。

(逆の問題は問題ではありません。MSYS2 は Windows 上で Unix スタイルの行末で正常に動作します。)

答え2

使用すべきbinfmt_その他そのために[1]。

まず、 で始まるファイルを処理するマジックを定義し#! /bin/bash<CR><LF>、次にその実行可能なインタープリタを作成します。インタープリタは別のスクリプトにすることができます。

INTERP=/path/to/bash-crlf

echo ",bash-crlf,M,,#! /bin/bash\x0d\x0a,,$INTERP," > /proc/sys/fs/binfmt_misc/register
cat > "$INTERP" <<'EOT'; chmod 755 "$INTERP"
#! /bin/bash
script=$1; shift; exec bash <(sed 's/\r$//' "$script") "$@"
EOT

試して:

$ printf '%s\r\n' '#! /bin/bash' pwd >/tmp/foo; chmod 755 /tmp/foo
$ cat -v /tmp/foo
#! /bin/bash^M
pwd^M
$ /tmp/foo
/tmp

サンプルインタープリタには 2 つの問題があります。1.スクリプトはシーク不可能なファイル(パイプ)を介して渡されるため、bashはそれをバイトごとに読み取るため、非常に非効率的であり、2.エラー メッセージ/dev/fd/63では、元のスクリプトの名前ではなく、 または同様の名前が参照されます。

[1] もちろん、binfmt_miscを使用する代わりに、/bin/bash^Mインタープリタへのシンボリックリンクを作成することもできます。これは、OpenBSDなどの他のシステムでも機能します。

ln -s /path/to/bash-crlf $'/bin/bash\r'

しかし、Linux では、shebanged 実行可能ファイルは binfmt_misc よりも優れているわけではなく、システム ディレクトリ内にゴミを置くことは正しい戦略ではなく、システム管理者なら誰でも首をかしげるでしょう ;-)

答え3

さて、私は次のような回避策を見つけました:

「ジャンクション」シンボリックリンク

現代の UNIX システムには、保存方法に関係なく、任意のデータをファイルとして表示する方法があります。ヒューズFUSEでは、ファイルに対するあらゆる操作(作成、開く、読み取り、書き込み、ディレクトリの一覧表示など)がプログラム内の何らかのコードを呼び出し、そのコードで好きなことを実行できます。実際にはコマンドである仮想ファイルを作成する試してみるといいでしょうスクリプトまたはヒューズ、または、意欲があれば、自分で作ってみましょう。

... そして実際にはコマンドである仮想ファイルを作成する

あなたが探しているのは名前付きパイプ

したがって、このアプローチは次のようになります。名前付きパイプを作成し、dos2unixそれに出力してから、bash名前付きパイプを呼び出します。

tmp.shここでは、 ;で終わる CRLF 行を持つオリジナルがあります。/tmpまず、名前付きパイプを作成しましょう。

tmp$ mkfifo ftmp.sh

ここで、このコマンドを実行します。

tmp$ dos2unix <tmp.sh >ftmp.sh

... ブロックされていることに気づくでしょう。その場合は、次のように言います。

~$ cat /tmp/ftmp.sh | hexdump -C
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0a 73  |echo "testing".s|
00000010  74 61 74 20 2e 0a 65 63  68 6f 20 22 74 65 73 74  |tat ..echo "test|
00000020  69 6e 67 20 61 67 61 69  6e 22 0a                 |ing again".|
0000002b

... 変換が完了したことがわかります。catコマンドの実行が完了すると、dos2unix <tmp.sh >ftmp.sh以前にブロックされていたコマンドは終了しています。

dos2unixしたがって、名前付きパイプへの書き込みを「無限」の while ループで設定できます。

tmp$ while [ 1 ] ; do dos2unix <tmp.sh >ftmp.sh ; done

... たとえそれが「タイトな」ループであっても、ほとんどの場合 while ループ内のコマンドがブロックされているため、問題にはなりません。

そうすると、次のことができます:

~$ bash /tmp/ftmp.sh
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: 801h/2049d  Inode: 5276132     Links: 7
...
testing again
$

...そして明らかに、スクリプトは正常に実行されます。

この方法の良いところは、元のコードtmp.shをテキスト エディターで開いて、CRLF で終わる新しいコードを書いて保存しtmp.shbash /tmp/ftmp.shLinux で実行すると最新の保存バージョンが実行されることです。

これの問題は、read -p "Enter user: " user実際の端末のstdinに依存するようなコマンドは失敗するということです。むしろ失敗しないのですが、試す場合は次のようにします。/tmp/tmp.sh

echo "testing"
stat .
echo "testing again"
read -p "Enter user: " user
echo "user is: $user"

... 次のように出力されます:

$ bash /tmp/ftmp.sh
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
...
 Birth: -
testing again
Enter user: tyutyu
user is: tyutyu
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
...
 Birth: -
testing again
Enter user: asd
user is: asd
testing
...

... などなど - つまり、ターミナルのキーボードからの stdin は正しく解釈されますが、何らかの理由でスクリプトがループを開始し、最初から何度も繰り返し実行されます (read -p ...元の にコマンドがない場合には発生しませんtmp.sh)。おそらく、これを処理できるリダイレクト機能 (たとえば、ループ コマンドにsome0>1&などwhileを追加するなど。実際、同じようにループを開始する.shスクリプトがあり、スクリプトの最後に明示的に を追加するだけで、スクリプトのループが停止するようです) があるかもしれませんが、これまでのところ、使用する必要があるスクリプトには同様のコマンドがないため、このアプローチが機能する可能性があります。wgetexit.shread -p

答え4

bash スクリプトの各行の末尾にハッシュ (#) を挿入することができます。このようにすると、Unix のシェルは CR を単なるコメントとして扱い、気にしなくなります。

「16進数で言えば、どの行も次のように終わる必要があります。

0x23 0x0D 0x0A

例:

echo "testing" #
stat . #
echo "testing again" #

関連情報