La forma de usar `/ usr / bin / env sed -f` en shebang?

25

Escribiendo /usr/bin/env sed -fen terminal funciona.

Pero si lo usas como un shebang,

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

El script no se ejecutará:

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

Creo que está relacionado con -f. ¿Pero cómo resolver esto?

Cheng
fuente
stackoverflow.com/questions/4303128/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

33

No puede, de forma portátil, poner más de un argumento en una #!línea . Eso significa solo una ruta completa y un argumento (p. Ej. #!/bin/sed -fO #!/usr/bin/sed -f), o #!/usr/bin/envsin argumento para el intérprete.

Una solución para obtener un script portátil es usar #!/bin/shun contenedor de shell, pasando el script sed como un argumento de línea de comandos. Tenga en cuenta que POSIX no lo autoriza (los scripts de instrucciones múltiples deben escribirse con un -eargumento separado para cada instrucción de portabilidad), pero funciona con muchas implementaciones.

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

Para una secuencia de comandos larga, puede ser más conveniente usar un heredoc. Una ventaja de un heredoc es que no necesita citar las comillas simples dentro, si las hay. Una desventaja importante es que el script se alimenta a sed en su entrada estándar, con dos consecuencias molestas. Algunas versiones de sed requieren en -f /dev/stdinlugar de -f -, lo cual es un problema para la portabilidad. Peor aún, el script no puede actuar como un filtro, porque la entrada estándar es el script y no puede ser la información.

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

La desventaja del heredoc puede remediarse mediante un uso útil de cat. Dado que esto vuelve a colocar todo el script en la línea de comando, no es compatible con POSIX, sino que es prácticamente portátil en la práctica.

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

Otra solución es escribir un script que pueda analizarse tanto por sh como por sed. Esto es portátil, razonablemente eficiente, solo un poco feo.

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

Explicaciones:

  • Bajo sh: define una función llamada b; el contenido no importa siempre que la función esté sintácticamente bien formada (en particular, no puede tener una función vacía). Entonces, si es verdadero (es decir, siempre), ejecute seden el script.
  • Bajo sed: se ramifica a la ()etiqueta, luego una entrada bien formada. Luego, un icomando, que no tiene ningún efecto porque siempre se omite. Finalmente la ()etiqueta seguida de la parte útil del guión.
  • Probado bajo GNU sed, BusyBox y OpenBSD. (Puede salirse con la suya con algo más simple en GNU sed, pero OpenBSD sed es exigente con las partes que omite).
Gilles 'SO- deja de ser malvado'
fuente
1
"o #!/usr/bin/envy sin argumento". No está redactado muy bien. Quizás "o #!/usr/bin/env sedno hay argumento para sed".
cjm
+1 para "un poco feo" y la secuencia de comandos de doble idioma :)
volver a reproducir
6

Hay varias implementaciones incompatibles de shebang (#!) Dependiendo del sistema operativo. Algunos están construyendo una lista completa de argumentos, algunos conservan la ruta del comando y colocan todos los argumentos restantes como uno solo, algunos ignoran todos los argumentos y pasan solo la ruta del comando, y finalmente, algunos pasan la cadena completa como un solo mando. Parece que estás en el último caso.

jlliagre
fuente
2

env está tratando de encontrar un archivo con el nombre "sed -f". Puede probar "#! / Usr / bin / sed -f" como su línea shebang.

anilmwr
fuente
2

A partir de GNU coreutils v8.30 , puede hacer:

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

Esta característica se agregó en una reciente (04/20/2018) se comprometen a env.cen el paquete GNU coreutils, que añadió el -So --split-stringopción.

Desde la envpágina del manual:

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.

Hay más ejemplos disponibles en el manual de GNU coreutils .

Si también usa la -vopción de salida detallada, puede ver exactamente cómo envdivide la cadena de argumentos:

En my_sed_script.sed:

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

Ejecución:

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

Nota: Esto solo se aplica a shebangs que usan /usr/bin/env, ya que --split-stringes una característica envespecífica de GNU .

Amin Mesbah
fuente
1

Esta respuesta proporciona un camino hacia una solución elegante: /programming//a/1655389/642372

  1. read un heredoc en una variable de shell.
  2. Pase esa variable como argumento posicional a sed.

Muestra:

#!/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
fuente
1

Me gusta la solución de Walker, pero se puede mejorar (poniendo esto en una respuesta separada porque los comentarios no aceptan texto preformateado). Es posible que esto no funcione en todas las versiones de Linux o Bash, pero en Ubuntu 17.10 puede reducir esto a lo siguiente:

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

Puede eliminar evaly simplificar el readcomando, pero debe deshacerse del comentario dentro del heredoc.

Además, para todo lo que siempre quiso saber sobre sed pero tenía miedo de preguntar, hay un tutorial muy útil en http://www.grymoire.com/unix/sed.html . Esto sugiere una solución aún mejor, a expensas de perder /usr/bin/envy codificar el camino hacia sed:

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

Esto tiene la ventaja adicional de admitir más de un s///reemplazo.

Huw Walters
fuente