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の何かがEnterに停止したようです。私は見た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)

(それらの間のギャップはそれほど大きくないようです...)

\rとにかく私の質問は - Ubuntu / LinuxのBashを「印刷可能な文字」として解釈するのではなく、これを無視するように説得する方法はありますか?こう説明します)?編集する:いいえスクリプト自体を変換する必要があります(たとえば、gitでこの方法でチェックすると、CRLF行の末尾に変更されていません)。

EDIT2:私は一緒に作業している他の人が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が関連している場合は、autocrlf設定フラグを使用してコミット時に行末を自動的に変換するGitの機能に依存するのが一般的な方法です。例を見る行末のGitHubドキュメント、これはGitHubに限定されません。このようにして、ファイルはリポジトリからUnixスタイルの行末にコミットされ、各クライアントプラットフォームに合わせて変換されます。

(その逆は問題ではありません。MSYS2はWindowsでUnixスタイルの行末をうまく処理します。)

答え2

使用する必要がありますbinfmt_miscこのような理由で[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] もちろん、/bin/bash^Mbinfmt_misc を使用する代わりに、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.shLinuxで保存bash /tmp/ftmp.shされた最新バージョンが実行されます。

この問題は、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
...

...等 - つまり、端末のキーボードの標準入力が正しく解釈されますが、何らかの理由でスクリプトが繰り返しを開始し、最初から何度も何度も実行されます(私たちがそうでなければこれは起こりません)read -p ...)original)コマンド使用してくださいtmp.sh。おそらくいくつかのリダイレクトエントリがあるかもしれません(ループコマンドに何かを追加することのような0>1&ものですwhile.sh;実際にこのようにループを開始するスクリプトがありましたが、スクリプトのwget最後に明示的を追加するだけでスクリプトのループを停止するようです)。また可能です。 - しかし、これまで私が使用しなければならないスクリプトには同様のコマンドがないので、このアプローチは私に適しています。exit.shread -p

答え4

Bashスクリプトの各行の末尾にポンド記号(#)を挿入できます。このように、UnixのシェルはCRをコメントアウトし、気にしません。

"hex"、すべての行は次に終了する必要があります。

0x23 0x0D 0x0A

例:

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

関連情報