Wie benutzt man `/ usr / bin / env sed -f` in shebang?

25

Das Eintippen /usr/bin/env sed -fdes Terminals funktioniert.

Aber wenn man es als Schafskopf benutzt,

#!/usr/bin/env sed -f 
s/a/b/

Das Skript kann nicht ausgeführt werden:

/usr/bin/env: sed -f: No such file or directory

Ich glaube irgendwie, dass es mit -f zusammenhängt. Aber wie lässt sich das beheben?

Cheng
quelle

Antworten:

33

Sie können tragbarerweise nicht mehr als ein Argument in eine #!Zeile setzen . Das bedeutet nur einen vollständigen Pfad und ein Argument (z. B. #!/bin/sed -foder #!/usr/bin/sed -f) oder #!/usr/bin/envkein Argument für den Interpreter.

Eine Problemumgehung, um ein portables Skript zu erhalten, ist die Verwendung #!/bin/sheines Shell-Wrappers, der das sed-Skript als Befehlszeilenargument übergibt. Beachten Sie, dass dies von POSIX nicht genehmigt wird (Multianweisungsskripte müssen aus Gründen der -ePortabilität für jede Anweisung mit einem separaten Argument geschrieben werden ), aber es funktioniert mit vielen Implementierungen.

#!/bin/sh
exec sed '
s/a/b/
' "$@"

Für ein langes Skript ist es möglicherweise praktischer, einen Heredoc zu verwenden. Ein Vorteil eines Heredocs ist, dass Sie die einzelnen Anführungszeichen nicht in Anführungszeichen setzen müssen, sofern vorhanden. Ein großer Nachteil ist, dass das Skript auf der Standardeingabe basiert, was zwei ärgerliche Folgen hat. Einige Versionen von sed erfordern -f /dev/stdinstattdessen -f -, was ein Problem für die Portabilität darstellt. Schlimmer noch, das Skript kann nicht als Filter fungieren, da die Standardeingabe das Skript ist und nicht die Daten sein können.

#!/bin/sh
exec sed -f - -- "$@" <<'EOF'
s/a/b/
EOF

Der Nachteil des Heredocs kann durch eine sinnvolle Verwendung von behoben werden cat. Da dadurch das gesamte Skript wieder in der Befehlszeile angezeigt wird, ist es nicht POSIX-kompatibel, aber in der Praxis weitgehend portierbar.

#!/bin/sh
exec sed "$(cat <<'EOF')" -- "$@"
s/a/b/
EOF

Eine andere Problemumgehung besteht darin, ein Skript zu schreiben, das sowohl von sh als auch von sed analysiert werden kann. Dies ist tragbar, einigermaßen effizient, nur ein wenig hässlich.

#! /bin/sh
b ()
{
x
}
i\
f true; then exec sed -f "$0" "$@"; fi
: ()
# sed script starts here
s/a/b/

Erklärungen:

  • Unter sh: definiere eine aufgerufene Funktion b; Der Inhalt spielt keine Rolle, solange die Funktion syntaktisch korrekt aufgebaut ist (insbesondere können Sie keine leere Funktion haben). Wenn dies der Fall ist (dh immer), führen Sie seddas Skript aus.
  • Unter sed: zum ()Label verzweigen , dann wohlgeformte Eingabe. Dann ein iBefehl, der keine Wirkung hat, weil er immer übersprungen wird. Schließlich das ()Etikett, gefolgt vom nützlichen Teil des Skripts.
  • Getestet unter GNU sed, BusyBox und OpenBSD. (Sie können mit etwas Einfacherem in GNU sed davonkommen, aber OpenBSD sed ist wählerisch in Bezug auf die Teile, die es überspringt.)
Gilles 'SO - hör auf böse zu sein'
quelle
1
"oder #!/usr/bin/envund kein Argument." ist nicht sehr gut formuliert. Vielleicht "oder #!/usr/bin/env sedund kein Argument zu sed."
cjm
+1 für "nur ein bisschen hässlich" und das
zweisprachige
6

Abhängig vom Betriebssystem gibt es verschiedene inkompatible Implementierungen von shebang (#!). Einige erstellen eine vollständige Argumentliste, andere behalten den Befehlspfad bei und geben alle verbleibenden Argumente als ein einziges Argument an, andere ignorieren alle Argumente und übergeben nur den Befehlspfad, und andere übergeben die gesamte Zeichenfolge als ein einziges Argument Befehl. Sie scheinen im letzteren Fall zu sein.

jlliagre
quelle
2

env versucht eine Datei mit dem Namen "sed -f" zu finden. Sie können "#! / Usr / bin / sed -f" als Ihre Shebang-Zeile verwenden.

anilmwr
quelle
2

Ab GNU coreutils v8.30 können Sie Folgendes tun:

#!/usr/bin/env -S sed -f

Diese Funktion wurde in einem kürzlich (2018.04.20) hinzugefügt begehen zu env.cin dem GNU coreutils - Paket, das die zusätzlichen -Soder --split-stringOption.

Von der envManpage:

OPTIONS
-S/--split-string usage in scripts
    The  -S  option allows specifing multiple parameters in a script.
    Running a script named 1.pl containing the following first line:

            #!/usr/bin/env -S perl -w -T

    Will execute perl -w -T 1.pl .

    Without the '-S' parameter the script will likely fail with:

            /usr/bin/env: 'perl -w -T': No such file or directory

    See the full documentation for more details.

Weitere Beispiele finden Sie im GNU coreutils-Handbuch .

Wenn Sie die -vOption auch für die ausführliche Ausgabe verwenden, können Sie genau sehen, wie envdie Zeichenfolge der Argumente aufgeteilt wird:

In my_sed_script.sed:

#!/usr/bin/env -vS sed -f
s/a/b/

Ausführung:

$ ./my_sed_script.sed
split -S:  ‘sed -f’
 into:    ‘sed’
     &    ‘-f’
executing: sed
   arg[0]= ‘sed’
   arg[1]= ‘-f’
   arg[2]= ‘./my_sed_script.sed’

Hinweis: Dies gilt nur für Shebangs, die verwendet werden /usr/bin/env, da dies --split-stringeine envspezielle Funktion von GNU ist .

Amin Mesbah
quelle
1

Diese Antwort bietet einen Weg zu einer eleganten Lösung: /programming//a/1655389/642372

  1. read ein Heredoc in eine Shell-Variable.
  2. Übergeben Sie diese Variable als Positionsargument an sed.

Probe:

#!/usr/bin/env bash

read -rd '' SED_SCRIPT <<EOD      
# Your sed script goes here.
s/a/b/
EOD

exec sed "$SED_SCRIPT" "$@"
Walker Hale IV
quelle
1

Ich mag die Lösung von Walker, aber sie kann verbessert werden (in einer separaten Antwort, da Kommentare keinen vorformatierten Text akzeptieren). Dies funktioniert möglicherweise nicht unter allen Linux- oder Bash-Versionen, aber unter Ubuntu 17.10 können Sie dies auf Folgendes reduzieren:

#!/usr/bin/env bash
read SED_SCRIPT <<EOD
s/a/b/
EOD
sed "$SED_SCRIPT" "$@"

Sie können evalden readBefehl entfernen und vereinfachen , müssen jedoch den Kommentar im heredoc entfernen .

Außerdem finden Sie unter http://www.grymoire.com/unix/sed.html ein nützliches Tutorial für alles, was Sie schon immer über sed wissen wollten, aber noch keine Fragen hatten . Dies schlägt eine noch bessere Lösung vor, auf Kosten des Verlusts /usr/bin/envund der Hardcodierung des Pfades zu sed:

#!/bin/sed -f
s/a/b/
s/c/d/

Dies hat den zusätzlichen Vorteil, dass mehr als ein s///Ersatz unterstützt wird.

Huw Walters
quelle