Trayectoria independiente shebangs

20

Tengo un script que quiero poder ejecutar en dos máquinas. Estas dos máquinas obtienen copias del script del mismo repositorio git. El script debe ejecutarse con el intérprete correcto (p. Ej.zsh ).

Desafortunadamente, ambos env yzsh vivir en diferentes lugares en los equipos locales y remotos:

Máquina remota

$ which env
/bin/env

$ which zsh
/some/long/path/to/the/right/zsh

Máquina local

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

¿Cómo puedo configurar el shebang para que ejecutar el script como /path/to/script.shsiempre use el Zshdisponible en PATH?

Amelio Vazquez-Reina
fuente
8
¿Estás seguro de envque no está en / bin y / usr / bin? Intenta which -a envconfirmar.
Grawity

Respuestas:

22

No puede resolver esto a través de shebang directamente, ya que shebang es puramente estático. Lo que podría hacer es tener algún »multiplicador menos común« (desde una perspectiva de shell) en el shebang y volver a ejecutar su script con el shell correcto, si este LCM no es zsh. En otras palabras: haga que su script sea ejecutado por un shell que se encuentra en todos los sistemas, pruebe una zshcaracterística única y si la prueba resulta falsa, tenga el script execcon zsh, donde la prueba tendrá éxito y simplemente continúe.

Una característica única en zsh, por ejemplo, es la presencia de la $ZSH_VERSIONvariable:

#!/bin/sh -

[ -z "$ZSH_VERSION" ] && exec zsh - "$0" ${1+"$@"}

# zsh-specific stuff following here
echo "$ZSH_VERSION"

En este caso simple, el script es ejecutado primero por /bin/sh(todos los sistemas similares a Unix posteriores a los 80 entienden #!y tienen un /bin/shBourne o POSIX, pero nuestra sintaxis es compatible con ambos). Si no$ZSH_VERSION está configurado, el script se procesa . Si está configurado (resp. El script ya se está ejecutandoexeczsh$ZSH_VERSIONzsh ), la prueba simplemente se omite. Voilà.

Esto solo falla si zshno está $PATHen absoluto.

Editar: Para asegurarse de que, sólo execuna zshen los lugares habituales, se podría utilizar algo como

for sh in /bin/zsh \
          /usr/bin/zsh \
          /usr/local/bin/zsh; do
    [ -x "$sh" ] && exec "$sh" - "$0" ${1+"$@"}
done

Esto podría salvarlo accidentalmente execde algo $PATHque no es lo zshque está esperando.

Andreas Wiese
fuente
Me upvoted esto para la elegancia pero tiene, en principio, tener problemas de seguridad / de compatibilidad, si la primera zshen la $PATHque no es el que usted espera.
Ryan Reich
Intenté abordarlo. La pregunta es si siempre puede estar seguro de si un zshbinario en las ubicaciones estándar es realmente un zsh.
Andreas Wiese el
Puede enrutar dinámicamente la línea! Bang. También puede preguntarse zshdónde está zsh -c 'whence zsh'. Más simplemente puedes simplemente command -v zsh. Vea mi respuesta para saber cómo direccionar dinámicamente el #!bang.
mikeserv
1
Llamar al zshbinario desde $PATHpara obtener la ruta del zshbinario no resolvería el problema que señaló @RyanReich, ¿verdad? :)
Andreas Wiese
No si te ejecutas zsh, no, supongo que no. Pero si inserta la cadena resultante en su hash bang y luego ejecuta su propio script, al menos sabrá lo que está obteniendo. Aún así, sería una prueba más simple que hacer un bucle.
mikeserv
7

Durante años he usado algo similar para tratar con las diversas ubicaciones de Bash en los sistemas que necesitaba para ejecutar mis scripts.

Bash / Zsh / etc.

#!/bin/sh

# Determines which OS and then reruns this script with approp. shell interp.
LIN_BASH="/bin/sh";
SOL_BASH="/packages/utilities/bin/sun5/bash";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  $SOL_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
elif [ $OS_TYPE = "Linux" ]; then
  $LIN_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
else
  echo "UNKNOWN OS_TYPE, $OS_TYPE";
  exit 1;
fi
exit 0;

### BEGIN

...script goes here...

Lo anterior se puede adaptar fácilmente para una variedad de intérpretes. La pieza clave es que este script inicialmente se ejecuta como shell Bourne. Luego se llama recursivamente por segunda vez, pero analiza todo por encima del comentario ### BEGINusando sed.

Perl

Aquí hay un truco similar para Perl:

#!/bin/sh

LIN_PERL="/usr/bin/perl";
SOL_PERL="/packages/perl/bin/perl";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  eval 'exec $SOL_PERL -x -S $0 ${1+"$@"}';
elif [ $OS_TYPE = "Linux" ]; then
  eval 'exec $LIN_PERL -x -S $0 ${1+"$@"}';
else
  echo "$OS_TYPE: UNSUPORRTED OS/PLATFORM";
  exit 0;
fi
exit 0;

#!perl

...perl script goes here...

Este método hace uso de la capacidad de Perl cuando se le da un archivo para ejecutar analizará dicho archivo omitiendo todas las líneas que están antes de la línea #! perl.

slm
fuente
Una serie de problemas allí: citas faltantes, uso de en $*lugar de "$@", uso inútil de eval, estado de salida no informado (no usó execpara el primero), mensajes de error faltantes -/ --no en stderr, 0 estado de salida para condiciones de error , usando / bin / sh para LIN_BASH, punto y coma inútil (cosmético), usando todo en mayúsculas para las variables que no son env. uname -ses como uname(uname es para nombre Unix). Olvidó mencionar que la opción activa la omisión . -xperl
Stéphane Chazelas
4

NOTA: @ jw013 hace la siguiente objeción no admitida en los comentarios a continuación:

El voto negativo se debe a que el código de auto modificación generalmente se considera una mala práctica. En los viejos tiempos de los pequeños programas de ensamblaje, era una forma inteligente de reducir las ramas condicionales y mejorar el rendimiento, pero hoy en día los riesgos de seguridad superan las ventajas. Su enfoque no funcionaría si el usuario que ejecutó el script no tuviera privilegios de escritura en el script.

Respondí sus objeciones de seguridad, señalando que cualquier permisos especiales sólo se requiere una vez por instalación / actualización de la acción con el fin de instalar / actualizar el autoinstalable guión - que personalmente yo llamaría bastante segura. También le señalé una man shreferencia para lograr objetivos similares por medios similares. En ese momento, no me molesté en señalar que, independientemente de las fallas de seguridad o de otras prácticas generalmente desaconsejadas que puedan o no estar representadas en mi respuesta, es más probable que se basaran en la pregunta en sí misma que en mi respuesta:

¿Cómo puedo configurar el shebang para que ejecutar el script como /path/to/script.sh siempre use el Zsh disponible en PATH?

No satisfecho, @ jw013 continuó objetando promoviendo su argumento aún no respaldado con al menos un par de declaraciones erróneas:

Utiliza un solo archivo, no dos archivos. El paquete [ man shreferenciado] tiene un archivo para modificar otro archivo. Tienes un archivo que se modifica a sí mismo. Hay una clara diferencia entre estos dos casos. Un archivo que toma entrada y produce salida está bien. Un archivo ejecutable que cambia a sí mismo mientras se ejecuta es generalmente una mala idea. El ejemplo que señaló no hace eso.

En primer lugar:

EL ÚNICO CÓDIGO EJECUTABLE EN CUALQUIER ESCRITO SHELL EJECUTABLE ES EL #!MISMO

(aunque incluso no #!está oficialmente especificado )

{   cat >|./file 
    chmod +x ./file 
    ./file
} <<-\FILE
    #!/usr/bin/sh
    {   ${l=lsof -p} $$
        echo "$l \$$" | sh
    } | grep \
        "COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE

##OUTPUT

COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
file    8900 mikeserv  txt    REG   0,33   774976  2148676 /usr/bin/bash
file    8900 mikeserv  mem    REG   0,30           2148676 /usr/bin/bash (path dev=0,33)
file    8900 mikeserv    0r   REG   0,35      108 15496912 /tmp/zshUTTARQ (deleted)
file    8900 mikeserv    1u   CHR  136,2      0t0        5 /dev/pts/2
file    8900 mikeserv    2u   CHR  136,2      0t0        5 /dev/pts/2
file    8900 mikeserv  255r   REG   0,33      108  2134129 /home/mikeserv/file
COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
sh      8906 mikeserv  txt    REG   0,33   774976  2148676 /usr/bin/bash
sh      8906 mikeserv  mem    REG   0,30           2148676 /usr/bin/bash (path dev=0,33)
sh      8906 mikeserv    0r  FIFO    0,8      0t0 15500515 pipe
sh      8906 mikeserv    1w  FIFO    0,8      0t0 15500514 pipe
sh      8906 mikeserv    2u   CHR  136,2      0t0        5 /dev/pts/2

{    sed -i \
         '1c#!/home/mikeserv/file' ./file 
     ./file 
     sh -c './file ; echo'
     grep '#!' ./file
}

##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links

#!/home/mikeserv/file

Un script de shell es solo un archivo de texto; para que tenga algún efecto, debe ser leído por otro archivo ejecutable, sus instrucciones luego interpretadas por ese otro archivo ejecutable, antes de que finalmente el otro archivo ejecutable ejecute su interpretación del script de shell No es posible que la ejecución de un archivo de script de shell implique menos de dos archivos. Hay una posible excepción en zshel compilador propio, pero con esto tengo poca experiencia y de ninguna manera está representado aquí.

El hashbang de un script de shell debe apuntar a su intérprete previsto o debe descartarse como irrelevante.

EL COMPORTAMIENTO DE RECONOCIMIENTO / EJECUCIÓN DE SHELL'S TOKEN ESTÁ DEFINIDO POR LAS NORMAS

El shell tiene dos modos básicos de analizar e interpretar su entrada: o su entrada actual está definiendo a <<here_documento está definiendo a { ( command |&&|| list ) ; } &- en otras palabras, el shell interpreta un token como un delimitador para un comando que debe ejecutar una vez que lo ha leído en o como instrucciones para crear un archivo y asignarlo a un descriptor de archivo para otro comando. Eso es.

Al interpretar comandos para ejecutar el shell delimita tokens en un conjunto de palabras reservadas. Cuando la carcasa se encuentra una abertura de contadores debe continuar a leer en una lista de comandos hasta que la lista es o bien delimitada por un cierre de contadores, tal como un salto de línea - en su caso - o el cierre token de como })para ({antes de la ejecución.

El shell distingue entre un comando simple y un comando compuesto. El comando compuesto es el conjunto de comandos que deben leerse antes de la ejecución, pero el shell no se ejecuta $expansionen ninguno de sus comandos simples constituyentes hasta que ejecuta individualmente cada uno.

Entonces, en el siguiente ejemplo, las ;semicolon palabras reservadas delimitan comandos simples individuales mientras que el carácter no escapado \newlinedelimita entre los dos comandos compuestos:

{   cat >|./file
    chmod +x ./file
    ./file
} <<-\FILE
        #!/usr/bin/sh
        echo "simple command ${sc=1}" ;\
                : > $0 ;\
                echo "simple command $((sc+2))" ;\
                sh -c "./file && echo hooray"
        sh -c "./file && echo hooray"
#END
FILE

##OUTPUT

simple command 1
simple command 3
hooray

Esa es una simplificación de las pautas. Se vuelve mucho más complicado cuando se consideran los componentes integrados de shell, subshell, entorno actual, etc., pero, para mis propósitos aquí, es suficiente.

Y hablando de listas incorporadas y de comandos, a function() { declaration ; }es simplemente un medio de asignar un comando compuesto a un comando simple. El shell no debe realizar ninguna $expansionsen la declaración de declaración en sí, para incluir <<redirections>, sino que debe almacenar la definición como una cadena literal única y ejecutarla como un shell especial incorporado cuando se le solicite.

Por lo tanto, una función de shell declarada en un script de shell ejecutable se almacena en la memoria del shell de interpretación en su forma de cadena literal, sin expandir para incluir documentos adjuntos aquí como entrada, y se ejecuta independientemente de su archivo fuente cada vez que se llama como un shell incorporado. durante el tiempo que dure el entorno actual del shell.

A <<HERE-DOCUMENTES UN ARCHIVO EN LÍNEA

Los operadores de redirección <<y <<-ambos permiten la redirección de líneas contenidas en un archivo de entrada de shell, conocido como documento aquí, a la entrada de un comando.

El documento aquí se tratará como una sola palabra que comienza después de la siguiente \newliney continúa hasta que haya una línea que contenga solo el delimitador y a \newline, sin [:blank:]s en el medio. Entonces comienza el siguiente documento aquí , si hay uno. El formato es el siguiente:

[n]<<word
    here-document 
delimiter

... donde lo opcional nrepresenta el número de descriptor de archivo. Si se omite el número, el documento aquí se refiere a la entrada estándar (descriptor de archivo 0).

for shell in dash zsh bash sh ; do sudo $shell -c '
        {   readlink /proc/self/fd/3
            cat <&3
        } 3<<-FILE
            $0

        FILE
' ; done

#OUTPUT

pipe:[16582351]
dash

/tmp/zshqs0lKX (deleted)
zsh

/tmp/sh-thd-955082504 (deleted)
bash

/tmp/sh-thd-955082612 (deleted)
sh

¿Lo ves? Para cada shell arriba, el shell crea un archivo y lo asigna a un descriptor de archivo. En zsh, (ba)shel shell crea un archivo normal /tmp, descarga la salida, la asigna a un descriptor, luego elimina el /tmparchivo para que la copia del descriptor del núcleo sea todo lo que queda. dashevita todas esas tonterías y simplemente deja caer su procesamiento de salida en un |pipearchivo anónimo dirigido al <<objetivo de redireccionamiento .

Esto hace que dash:

cmd <<HEREDOC
    $(cmd)
HEREDOC

funcionalmente equivalente a bash's:

cmd <(cmd)

mientras que dashla implementación es al menos POSIXly portable.

QUE HACE VARIOS ARCHIVOS

Entonces, en la respuesta a continuación cuando lo hago:

{    cat >|./file
     chmod +x ./file
     ./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat 
} <<SCRIPT >$0
    [SCRIPT BODY]
SCRIPT    

_fn ; exec $0
FILE

Sucede lo siguiente:

  1. La primera vez que catel contenido de cualquier archivo de la cáscara creado por FILEdentro ./file, hacerlo ejecutable, a continuación, ejecutarlo.

  2. El núcleo interpreta las #!llamadas y /usr/bin/shcon un <read descriptor de archivo asignado ./file.

  3. shasigna una cadena a la memoria que consiste en el comando compuesto que comienza en _fn()y termina en SCRIPT.

  4. Cuando _fnse llama, shprimero debe interpretar a continuación, asignar a un descriptor de archivo definido en <<SCRIPT...SCRIPT antes de invocar _fncomo un especial utilidad integrada porque SCRIPTes _fn's<input.

  5. La salida de cadenas por printfy commandse escriben en _fn's estándar de salida >&1 - que es redirigido a la shell actual de ARGV0- o $0.

  6. catconcatena su descriptor de archivo de <&0 entrada estándarSCRIPT - sobre el argumento >del shell actual truncado ARGV0, o $0.

  7. Completando su comando compuesto actual ya leído , se encuentra sh execel $0argumento ejecutable y recientemente reescrito .

Desde el momento en que ./filese llama hasta que sus instrucciones contenidas especifiquen que debe volverse execa shleer , lo lee en un solo comando compuesto a la vez mientras los ejecuta, mientras que en ./filesí mismo no hace nada excepto aceptar felizmente sus nuevos contenidos. Los archivos que están realmente en el trabajo son/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.

GRACIAS, DESPUÉS DE TODO

Entonces cuando @ jw013 especifica que:

Un archivo que toma entrada y produce salida está bien ...

... en medio de su crítica errónea de esta respuesta, en realidad está perdonando involuntariamente el único método utilizado aquí, que básicamente funciona solo para:

cat <new_file >old_file

RESPONDER

Todas las respuestas aquí son buenas, pero ninguna de ellas es completamente correcta. Todo el mundo parece afirmar que no puedes seguir tu camino de forma dinámica y permanente #!bang. Aquí hay una demostración de cómo configurar un camino independiente shebang:

MANIFESTACIÓN

{   cat >|./file
    chmod +x ./file
    ./file
} <<\FILE 
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
        ${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
        printf "
        \$0    :\t$0
        lines :\t$((c=$(wc -l <$0)))
        !bang :\t$(sed 1q "$0")
        shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
        sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
                sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE

SALIDA

        $0    : ./file
        lines : 13
        !bang : #!/usr/bin/sh
        shell : /usr/bin/sh

1 >     #!/usr/bin/sh
2 >     _rewrite_me() { printf '#!' ; command -v zsh
...
12 >    SCRIPT
13 >    _rewrite_me ; out=$0 _rewrite_me ; exec $0

        $0    : /home/mikeserv/file
        lines : 8
        !bang : #!/usr/bin/zsh
        shell : /usr/bin/zsh

1 >     #!/usr/bin/zsh
2 >             printf "
...
7 >             sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 >                     sed -e 'N;s/\n/ >\t/' -e 4a\\...

¿Lo ves? Simplemente hacemos que el script se sobrescriba. Y solo ocurre una vez después de una gitsincronización. A partir de ese momento, tiene el camino correcto en la línea #! Bang.

Ahora, casi todo eso allí arriba es solo pelusa. Para hacer esto de manera segura necesitas:

  1. Una función definida en la parte superior y llamada en la parte inferior que realiza la escritura. De esta forma, almacenamos todo lo que necesitamos en la memoria y nos aseguramos de que se lea todo el archivo antes de comenzar a escribir sobre él.

  2. Alguna forma de determinar cuál debería ser el camino. command -ves bastante bueno para eso

  3. Los heredocs realmente ayudan porque son archivos reales. Almacenarán su guión mientras tanto. También puedes usar cadenas pero ...

  4. Debe asegurarse de que el shell lea el comando que sobrescribe su script en la misma lista de comandos que el que lo ejecuta.

Mira:

{   cat >|./file
    chmod +x ./file
    ./file
} <<\FILE 
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
        ${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
        printf "
        \$0    :\t$0
        lines :\t$((c=$(wc -l <$0)))
        !bang :\t$(sed 1q "$0")
        shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
        sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
                sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE

Tenga en cuenta que solo moví el execcomando una línea hacia abajo. Ahora:

#OUTPUT
        $0    : ./file
        lines : 14
        !bang : #!/usr/bin/sh
        shell : /usr/bin/sh

1 >     #!/usr/bin/sh
2 >     _rewrite_me() { printf '#!' ; command -v zsh
...
13 >    _rewrite_me ; out=$0 _rewrite_me
14 >    exec $0

No obtengo la segunda mitad de la salida porque el script no puede leer en el siguiente comando. Aún así, porque el único comando que faltaba era el último:

cat ./file

#!/usr/bin/zsh
        printf "
        \$0    :\t$0
        lines :\t$((c=$(wc -l <$0)))
        !bang :\t$(sed 1q "$0")
        shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
        sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
                sed -e 'N;s/\n/ >\t/' -e 4a\\...

La secuencia de comandos se realizó como debería, principalmente porque estaba todo en el documento heredoc, pero si no lo planifica correctamente, puede truncar su flujo de archivos, que es lo que me sucedió anteriormente.

mikeserv
fuente
El voto negativo se debe a que el código de auto modificación generalmente se considera una mala práctica. En los viejos tiempos de los pequeños programas de ensamblaje, era una forma inteligente de reducir las ramas condicionales y mejorar el rendimiento, pero hoy en día los riesgos de seguridad superan las ventajas. Su enfoque no funcionaría si el usuario que ejecutó el script no tuviera privilegios de escritura en el script.
jw013
@ jw013 Obviamente, mi enfoque para instalar o actualizar un script ejecutable no funcionaría si la persona que intenta instalar o actualizar el script no tuviera permisos para instalarlo o actualizarlo. de hecho, eso es específicamente lo que hace que esta respuesta sea mejor que cualquier otra respuesta aquí: puede proporcionar una línea #! bang precisa según sea necesario y solo necesita permisos especiales para hacerlo en la primera invocación, durante la instalación. Y, una vez más, no voy a aceptar su palabra de que el código auto modificable es una mala práctica , por favor, vea man commanduna opinión contradictoria.
mikeserv
por favor vea man commanduna opinión contradictoria - no encontrar una. ¿Me puede dirigir a la sección / párrafo específico del que estaba hablando?
jw013
@ jw013 - mi error, está en man sh- busque 'command -v'. Sabía que estaba en una de las manpáginas que estaba mirando el otro día.
mikeserv
Supongo que este es el command -vejemplo del que estabas hablando man sh. Esa es una secuencia de comandos de instalación de aspecto normal, y no una modificación automática . Incluso los instaladores autónomos solo contienen entradas previas a la modificación y generan sus modificaciones en otro lugar. No se reescriben de la manera que usted recomienda.
jw013
1

Aquí hay una forma de tener un script auto modificable que arregle su shebang. Este código debe anteponerse a su script real.

#!/bin/sh
# unpatched

PATH=`PATH=/bin:/usr/bin:$PATH getconf PATH`
if [ "`awk 'NR==2 {print $2;exit;}' $0`" = unpatched ]; then
  [ -z "`PATH=\`getconf PATH\`:/usr/local/bin:/some/long/path/to/the/right:$PATH command -v zsh`" ] && { echo "zsh not found"; exit 1; }
  cp -- "$0" "$0.org" || exit 1
  mv -- "$0" "$0.old" || exit 1
  (
    echo "#!`PATH=\`getconf PATH\`:$PATH command -v zsh`" 
    sed -n '/^##/,$p' $0.old
  ) > $0 || exit
  chmod +x $0
  rm $0.old
  sync
  exit
fi
## Original script starts here

Algunos comentarios:

  • Debe ser ejecutado una vez por alguien que tenga los derechos para crear y eliminar archivos en el directorio donde se encuentra el script.

  • Solo utiliza la sintaxis de shell bourne heredada, ya que, a pesar de la creencia popular, /bin/shno se garantiza que sea un shell POSIX, incluso en sistemas operativos compatibles con POSIX.

  • Establece la RUTA en una que cumpla con POSIX seguida de una lista de posibles ubicaciones zsh para evitar elegir una zsh "falsa".

  • Si por alguna razón, un script auto modificable no es bienvenido, sería trivial distribuir dos scripts en lugar de uno, el primero es el que desea parchear y el segundo, el que sugerí ligeramente modificado para procesar el primero.

jlliagre
fuente
El /bin/shpunto es bueno, pero en ese caso, ¿necesitas una premodificación #!? ¿Y no es awktan probable que sea tan falso como lo zshes?
mikeserv
@mikeserv Respuesta actualizada para llamar al POSIX awk. El shebang premodificado está ahí para evitar que el script sea interpretado por un shell no compatible con bourne en caso de que sea su shell de inicio de sesión.
jlliagre
Tiene sentido. Lo voté porque funciona, se adhiere al libro y demuestra una buena comprensión del posible entorno de shell / manejo de archivos, especialmente los archivos de respaldo que usa, que de todos sed -imodos es todo lo que hace GNU . Personalmente, creo que el $PATHproblema observado en los comentarios sobre otra respuesta y que aborda de la manera más segura que puedo encontrar aquí en unas pocas líneas se maneja mejor definiendo dependencias de manera simple y explícita y / o pruebas rigurosas y explícitas, por ejemplo, ahora getconfpodría ser falso, pero las posibilidades son casi nulas, lo mismo que para ellos zshyawk.
mikeserv
@mikeserv, script modificado para reducir el riesgo de llamar a un getconf falso.
jlliagre
$(getconf PATH)No es Bourne. cp $0 $0.oldes la sintaxis zsh. El equivalente de Bourne sería cp "$0" "$0.old"lo que quisierascp -- "$0" "$0.old"
Stéphane Chazelas