Nehmen wir an, ich habe das folgende triviale Skript tmp.sh
:
echo "testing"
stat .
echo "testing again"
So trivial es auch ist, es hat \r\n
(also CRLF, also Wagenrücklauf+Zeilenvorschub) als Zeilenende. Da die Webseite die Zeilenenden nicht beibehält, hier ein Hexdump:
$ 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
Jetzt hat es CRLF-Zeilenenden, weil das Skript unter Windows unter MSYS2 gestartet und entwickelt wurde. Wenn ich es also unter Windows 10 in MSYS2 ausführe, erhalte ich das Erwartete:
$ 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
Wenn ich dieses Skript jedoch auf eine Ubuntu 18.04-Maschine kopiere und es dort ausführe, erhalte ich etwas anderes:
$ bash tmp.sh
testing
stat: cannot stat '.'$'\r': No such file or directory
testing again
In anderen Skripten mit denselben Zeilenenden habe ich in Ubuntu Bash auch diesen Fehler erhalten:
line 6: $'\r': command not found
... wahrscheinlich aus einer leeren Zeile.
Offensichtlich blockiert Ubuntu die Wagenrückläufe. Ich habe gesehen,BASH und Wagenrücklaufverhalten:
es hat nichts mit Bash zu tun: \r und \n werden vom Terminal interpretiert, nicht von Bash
... ich vermute jedoch, dass dies nur für Dinge gilt, die wörtlich in die Befehlszeile eingegeben werden; hier sind \r
und \n
bereits im Skript selbst eingegeben, daher muss Bash das \r
hier interpretieren.
Hier ist die Version von Bash in Ubuntu:
$ bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
... und hier die Version von Bash in MSYS2:
$ bash --version
GNU bash, version 4.4.23(2)-release (x86_64-pc-msys)
(sie scheinen nicht so weit voneinander entfernt zu sein ...)
Wie dem auch sei, meine Frage lautet: Gibt es eine Möglichkeit, Bash unter Ubuntu/Linux dazu zu bringen, das zu ignorieren \r
, anstatt zu versuchen, es als (sozusagen) „druckbares Zeichen“ zu interpretieren (in diesem Fall also als ein Zeichen, das Teil eines gültigen Befehls sein könnte und das Bash als solches interpretiert)? BEARBEITEN:ohnedas Skript selbst konvertieren zu müssen (damit es gleich bleibt, mit CRLF-Zeilenenden, wenn es beispielsweise in Git auf diese Weise geprüft wird)
EDIT2: Mir wäre es so lieber, weil andere Leute, mit denen ich arbeite, das Skript möglicherweise erneut im Windows-Texteditor öffnen, es möglicherweise \r\n
noch einmal in das Skript einführen und es committen könnten; und dann hätten wir am Ende möglicherweise einen endlosen Strom von Commits, die möglicherweise nichts anderes sind als Konvertierungen \r\n
zur \n
Verschmutzung des Repository.
EDIT2: @Kusalananda erwähnte in den Kommentaren dos2unix
( sudo apt install dos2unix
); beachten Sie, dass allein das Schreiben dieses:
$ dos2unix tmp.sh
dos2unix: converting file tmp.sh to Unix format...
... konvertiert die Datei direkt; um sie auf stdout auszugeben, muss man eine stdin-Umleitung einrichten:
$ 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
... und dann könnte man dies prinzipiell unter Ubuntu ausführen, was in diesem Fall zu funktionieren scheint:
$ 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
Allerdings ändert sich dadurch - abgesehen von dem etwas komplizierten Befehl, den man sich merken muss - auch die Bash-Semantik, da stdin kein Terminal mehr ist; dies mag bei diesem trivialen Beispiel funktioniert haben, aber siehe z. B.https://stackoverflow.com/questions/23257247/pipe-a-script-into-bashzum Beispiel bei größeren Problemen.
Antwort1
Soweit mir bekannt ist, gibt es keine Möglichkeit, Bash anzuweisen, Zeilenenden im Windows-Stil zu akzeptieren.
In Situationen mit Windows ist es gängige Praxis, sich auf die Fähigkeit von Git zu verlassen, Zeilenenden beim Commit automatisch zu konvertieren, indem das autocrlf
Konfigurationsflag verwendet wird. Siehe zum BeispielGitHubs Dokumentation zu Zeilenenden, was nicht spezifisch für GitHub ist. Auf diese Weise werden Dateien mit Zeilenenden im Unix-Stil in das Repository übernommen und für jede Client-Plattform entsprechend konvertiert.
(Das gegenteilige Problem tritt nicht auf: MSYS2 funktioniert unter Windows problemlos mit Zeilenenden im Unix-Stil.)
Antwort2
Du solltest benutzenbinfmt_miscdafür [1].
Definieren Sie zunächst eine Magie, die Dateien verarbeitet, die mit beginnen #! /bin/bash<CR><LF>
, und erstellen Sie dann einen ausführbaren Interpreter dafür. Der Interpreter kann ein anderes Skript sein:
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
Probier es aus:
$ 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
Der Beispielinterpreter weist zwei Probleme auf:1.da es das Skript über eine nicht suchbare Datei (eine Pipe) weiterleitet, wird Bash es Byte für Byte lesen, was sehr ineffizient ist, und2.Alle Fehlermeldungen beziehen sich auf /dev/fd/63
oder etwas Ähnliches anstelle des Namens des Originalskripts.
[1] Anstatt binfmt_misc zu verwenden, kann man natürlich auch einfach einen /bin/bash^M
symbolischen Link zum Interpreter erstellen, was auch auf anderen Systemen wie OpenBSD funktionieren würde:
ln -s /path/to/bash-crlf $'/bin/bash\r'
Aber unter Linux haben Shebang-Executables keinen Vorteil gegenüber binfmt_misc und Müll in Systemverzeichnissen zu lagern ist nicht die richtige Strategie und wird jeden Systemadministrator kopfschüttelnd zurücklassen ;-)
Antwort3
Ok, ich habe eine Art Workaround gefunden, und zwar über:
„Verknüpfte“ symbolische Links
Moderne Unix-Systeme verfügen über eine Möglichkeit, beliebige Daten als Datei erscheinen zu lassen, unabhängig davon, wie sie gespeichert sind:SICHERUNG. Mit FUSE ruft jede Operation an einer Datei (erstellen, öffnen, lesen, schreiben, Verzeichnis auflisten usw.) Code in einem Programm auf, und dieser Code kann alles tun, was Sie wollen. SieheErstellen Sie eine virtuelle Datei, die eigentlich ein Befehl istSie könnten versuchenSkriptfsoderSicherung, oder wenn Sie ehrgeizig sind, drehen Sie Ihr eigenes.
... UndErstellen Sie eine virtuelle Datei, die eigentlich ein Befehl ist
Vielleicht suchen Sie nach einemBenannte Pipe.
Das ist also der Ansatz: Erstellen Sie eine benannte Pipe, lassen Sie dos2unix
die Ausgabe dorthin senden und bash
rufen Sie dann die benannte Pipe auf.
Hier habe ich das Original tmp.sh
mit CRLF-Zeilenenden in /tmp
; zuerst erstellen wir die benannte Pipe:
tmp$ mkfifo ftmp.sh
Wenn Sie jetzt diesen Befehl ausführen:
tmp$ dos2unix <tmp.sh >ftmp.sh
... Sie werden feststellen, dass es blockiert. Wenn das der Fall ist, sagen Sie:
~$ 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
... werden Sie feststellen, dass die Konvertierung abgeschlossen ist – und nachdem der cat
Befehl seinen Lauf genommen hat, wurde der dos2unix <tmp.sh >ftmp.sh
Befehl, der zuvor blockiert hat, beendet.
dos2unix
So können wir das Schreiben in die benannte Pipe in einer „endlosen“ While-Schleife einrichten :
tmp$ while [ 1 ] ; do dos2unix <tmp.sh >ftmp.sh ; done
... und selbst wenn es eine „enge“ Schleife ist, sollte dies kein Problem darstellen, da der Befehl innerhalb der While-Schleife meistens blockiert.
Dann kann ich Folgendes tun:
~$ bash /tmp/ftmp.sh
testing
File: .
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 801h/2049d Inode: 5276132 Links: 7
...
testing again
$
... und offensichtlich läuft das Skript einwandfrei.
Das Gute an diesem Ansatz ist, dass ich das Original tmp.sh
in einem Texteditor öffnen und neuen Code schreiben kann (mit CRLF-Endung), dann speichern kann tmp.sh
und dass beim Ausführen bash /tmp/ftmp.sh
unter Linux die zuletzt gespeicherte Version ausgeführt wird.
Das Problem dabei ist, dass solche Befehle, read -p "Enter user: " user
die auf der tatsächlichen Standardeingabe des Terminals basieren, fehlschlagen; oder besser gesagt, nicht fehlschlagen, aber wenn Sie es versuchen, sagen Sie dies als/tmp/tmp.sh
echo "testing"
stat .
echo "testing again"
read -p "Enter user: " user
echo "user is: $user"
... dann wird folgendes ausgegeben:
$ 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
...
... und so weiter – das heißt, stdin von der Tastatur im Terminal wird richtig interpretiert, aber aus irgendeinem Grund beginnt das Skript zu schleifen und wird immer wieder von Anfang an ausgeführt (was nicht passiert, wenn wir read -p ...
im Original keinen Befehl haben tmp.sh
). Vielleicht gibt es Umleitungsmaterial (z. B. Hinzufügen von etwas ; tatsächlich hatte ich ein 0>1&
oder was auch immer zum while
Schleifenbefehl.sh
Skript , wget
das auch so zu schleifen begann, und das einfache Hinzufügen eines expliziten exit
am Ende des .sh
Skripts schien zu funktionieren, um die Skriptschleife zu stoppen), das dies ebenfalls handhaben könnte – aber bisher hat das Skript, das ich verwenden muss, keine read -p
derartigen Befehle, also könnte dieser Ansatz für mich funktionieren.
Antwort4
Sie können in Ihren Bash-Skripten einfach am Ende jeder Zeile ein Hash (#) einfügen. Auf diese Weise betrachten die Shells unter Unix den CR nur als Kommentar und kümmern sich nicht darum.
"Hex-sprechend" sollte jede Zeile mit enden
0x23 0x0D 0x0A
Beispiel:
echo "testing" #
stat . #
echo "testing again" #