Jak używać `/ usr / bin / env sed -f` w shebang?

25

Pisanie /usr/bin/env sed -fw terminalu działa.

Ale jeśli użyjesz go jako shebang,

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

Wykonanie skryptu nie powiedzie się:

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

Wierzę, że jest to związane z -f. Ale jak to rozwiązać?

Cheng
źródło
stackoverflow.com/questions/4303128/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

33

Nie można przenośnie umieścić więcej niż jednego argumentu w #!wierszu . Oznacza to tylko pełną ścieżkę i jeden argument (np. #!/bin/sed -fLub #!/usr/bin/sed -f) lub #!/usr/bin/envbrak argumentu dla tłumacza.

Obejściem, które pozwala uzyskać przenośny skrypt, jest użycie #!/bin/shi otoki powłoki, które przekazują skrypt sed jako argument wiersza poleceń. Zauważ, że nie jest to sankcjonowane przez POSIX (skrypty składające się z wielu instrukcji muszą być napisane z osobnym -eargumentem dla każdej instrukcji przenośności), ale działa z wieloma implementacjami.

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

W przypadku długiego skryptu wygodniejsze może być użycie heredoc. Zaletą heredoc jest to, że nie musisz cytować pojedynczych cudzysłowów, jeśli takie istnieją. Główną wadą jest to, że skrypt jest zasilany standardowym wejściem, co ma dwie irytujące konsekwencje. Niektóre wersje sed wymagają -f /dev/stdinzamiast tego -f -, co stanowi problem w zakresie przenośności. Co gorsza, skrypt nie może działać jako filtr, ponieważ standardowym wejściem jest skrypt i nie mogą to być dane.

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

Minusem heredoka można zaradzić poprzez przydatne użycie cat. Ponieważ powoduje to ponowne umieszczenie całego skryptu w wierszu poleceń, nie jest on zgodny z POSIX, ale w dużej mierze jest przenośny w praktyce.

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

Innym obejściem jest napisanie skryptu, który można przeanalizować zarówno przez sh, jak i przez sed. Jest przenośny, dość wydajny, tylko trochę brzydki.

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

Objaśnienia:

  • Pod sh: zdefiniuj funkcję o nazwie b; zawartość nie ma znaczenia, o ile funkcja jest poprawnie sformatowana pod względem składniowym (w szczególności nie można mieć pustej funkcji). Następnie, jeśli jest to prawda (tj. Zawsze), uruchom sedskrypt.
  • Pod sedem: przejdź do ()etykiety, a następnie dobrze sformułowany tekst. Następnie ipolecenie, które nie działa, ponieważ zawsze jest pomijane. Na koniec ()etykieta, po której następuje przydatna część skryptu.
  • Testowane pod GNU sed, BusyBox i OpenBSD. (Możesz uzyskać coś prostszego w GNU sed, ale OpenBSD sed jest wybredny w kwestii części, które pomija.)
Gilles „SO- przestań być zły”
źródło
1
„lub #!/usr/bin/envbez argumentu”. nie jest dobrze sformułowany. Być może „lub #!/usr/bin/env sedbez argumentu do sed”.
cjm
+1 za „tylko trochę brzydkie” i dwujęzyczny skrypt :)
retracile
6

Istnieje wiele różnych niekompatybilnych implementacji shebang (#!) W zależności od systemu operacyjnego. Niektórzy budują pełną listę argumentów, inni zachowują ścieżkę poleceń i umieszczają wszystkie pozostałe argumenty jako jeden, inni ignorują wszystkie argumenty i przekazują tylko ścieżkę poleceń, a na koniec niektórzy przekazują cały ciąg jako pojedynczy dowództwo. Wydaje się, że jesteś w tym drugim przypadku.

jlliagre
źródło
2

env próbuje znaleźć plik o nazwie „sed -f”. Możesz wypróbować „#! / Usr / bin / sed -f” jako linię shebang.

anilmwr
źródło
2

Od wersji GNU coreutils 8.30 możesz:

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

Ta cecha dodano w ostatnim (2018-04-20) zobowiązać do env.cW GNU Coreutils opakowania, które dodaje się do -Slub --split-stringopcji.

Ze strony podręcznika env:

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.

Więcej przykładów jest dostępnych w podręczniku GNU coreutils .

Jeśli użyjesz również -vopcji dla pełnych danych wyjściowych, możesz dokładnie zobaczyć, jak envdzieli ciąg argumentów:

W my_sed_script.sed:

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

Wykonanie:

$ ./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’

Uwaga: Dotyczy to tylko shebangów, które są używane /usr/bin/env, ponieważ --split-stringjest to właściwość GNU env.

Amin Mesbah
źródło
1

Ta odpowiedź zawiera ścieżkę do eleganckiego rozwiązania: /programming//a/1655389/642372

  1. read heredoc w zmienną powłoki.
  2. Przekaż tę zmienną jako argument pozycyjny do sed.

Próba:

#!/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
źródło
1

Podoba mi się rozwiązanie Walkera, ale można je ulepszyć (umieszczając to w osobnej odpowiedzi, ponieważ komentarze nie akceptują sformatowanego tekstu). To może nie działać na wszystkich wersjach Linuxa lub Basha, ale w Ubuntu 17.10 możesz to ograniczyć do następujących elementów:

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

Możesz usunąć evali uprościć readpolecenie, ale musisz pozbyć się komentarza w heredoc.

Ponadto, o wszystko, co kiedykolwiek chciałeś wiedzieć o sed, ale bałeś się zapytać, jest bardzo przydatny samouczek na stronie http://www.grymoire.com/unix/sed.html . To sugeruje jeszcze lepsze rozwiązanie, kosztem utraty /usr/bin/envi twardego kodowania ścieżki do sed:

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

Ma to tę dodatkową zaletę, że obsługuje więcej niż jedną s///wymianę.

Huw Walters
źródło