Programación de shell, evitando archivos temporales

8

A menudo escribo scripts de shell KSH que siguen el mismo patrón:

  • (1) recuperar la salida de uno o más comandos
  • (2) formatee con grep | cut | awk | sed e imprímalo en la pantalla o en un archivo

Para hacer eso, a menudo almaceno la salida de (1) en un archivo temporal, y luego hago el formato en (2) en ese archivo.

Tome ese código por ejemplo:

TMPFILE=file.tmp

# If tmpfile exists rm it.
[ -f $TMPFILE ] && rm -f $TMPFILE

for SERVICE in $(myfunc); do
    getInfo $SERVICE > $TMPFILE # Store raw output in the TMPFILE

    # I retrieve the relevant data from the TMPFILE
    SERV_NAME=$(head -1 $TMPFILE | sed -e 's/ $//')
    SERV_HOSTNAME=$(grep HOSTNAME $TMPFILE | cut -d "=" -f2)
    SERV_ARGS=$(grep Arguments $TMPFILE | cut -d ":" -f2)

    print $SERV_NAME $SEP $SERV_HOSTNAME $SEP $SERV_ARGS
    rm -f $TMPFILE #rm the TMPFILE in vue of next iteration
done

¿Hay alguna manera, usando tuberías, redirecciones y otras cosas, para evitar escribir un archivo en el disco cada vez?

Si ayuda, estoy usando ksh Versión M-11/16 / 88i

rahmu
fuente
3
Es una buena forma de evitar los nombres de variables ALL_CAPS en los scripts de shell, y tratar ese espacio de nombres como reservado por el shell para evitar tropezar con cosas importantes como PATHu otras variables de entorno o shell. TMPFILEpuede estar bien, pero TMPDIRes especial, entonces, ¿realmente quieres caminar por la cuerda floja?
jw013
Para la posteridad: otra pregunta que fue marcada como un duplicado de esta, unix.stackexchange.com/questions/63923/… incluye una respuesta que involucra las tuberías con nombre de Fifo, que también podría usarse aquí (aunque probablemente no sea la mejor opción en este caso particular)
Ricitos de oro
@goldilocks: Tal vez podamos fusionar las dos preguntas en una sola. ¿Podemos contactar a un moderador para hacer esto?
rahmu
@rahmu: Marqué la otra pregunta. Supongo que depende de los poderes ahora ...
goldilocks

Respuestas:

9

Su código parece un ejemplo completamente justificado de usar archivos temporales para mí. Me quedaría: seguir con este enfoque. Lo único que realmente necesita ser cambiado es la forma en que crea el archivo temporal. Usa algo como

 TMP=$(tempfile)

o

 TMP=$(mktemp)

o al menos

 TMP=/tmp/myscript_$$

De esta manera, no permitirá que el nombre se prediga fácilmente (seguridad) y excluya la interferencia de la regla entre varias instancias del script que se ejecutan al mismo tiempo.

rozcietrzewiacz
fuente
2
pedagógicamente, no se requieren comillas para la asignación de variables.
Glenn Jackman
1
@glenn Verdadero, en este caso no deberían marcar la diferencia, ya que cada uno de los comandos generalmente produce una cadena sin espacios. Pero es un buen hábito tener comillas en los casos en que asigna la salida del comando a una variable, por lo que persistiré en dejarlo de esta manera.
rozcietrzewiacz
Se eliminaron las comillas en el último ejemplo para distinción.
rozcietrzewiacz
3
@roz No, te perdiste el punto. Las asignaciones variables en shell se reconocen antes de que se realice cualquier expansión, y la división de campos NO se realiza para asignaciones variables. Por lo tanto, var=$(echo lots of spaces); echo "$var"está bien y debe producir lots of spacescomo salida. La advertencia real que nadie mencionó es que la sustitución de comandos elimina todas las nuevas líneas finales. Esto no es un problema aquí, y solo importa, por ejemplo, si tuvo un nombre roto mktempque creó nombres de archivo con líneas nuevas. La solución habitual, si es necesario, es var=$(echo command with trailing newline; echo x); var=${var%x}.
jw013
1
@ jw013 Sí, me doy cuenta de esto ahora, no lo hice, cuando escribí la respuesta hace un año. ¡Gracias por mencionarlo! (arreglando ...)
rozcietrzewiacz
5

Puedes usar una variable:

info="$(getInfo $SERVICE)"
SERV_NAME="$(head -1 $TMPFILE <<<"$info" | sed -e 's/ $//')"
...

De man ksh:

<<<word       A  short  form of here document in which word becomes the
              contents of the here-document after any parameter  expan-
              sion,  command  substitution, and arithmetic substitution
              occur.

Las ventajas incluyen:

  • Permite la ejecución paralela.
  • En mi experiencia, esto es mucho más rápido que los archivos temporales. A menos que tenga tantos datos que termine intercambiando, deberían ser órdenes de magnitud más rápidos (solo excluyendo los búferes de almacenamiento en caché HD, que podrían ser tan rápidos para pequeñas cantidades de datos).
  • Otros procesos o usuarios no pueden alterar sus datos.
l0b0
fuente
<<< no parece existir en mi ksh. Recibo un error y parece que no puedo encontrarlo en la página del manual. Estoy usando ksh88. ¿Estás seguro de que esta versión debería tener esta característica?
rahmu
No; Supongo que no revisé la manpágina correcta (no se mencionó el número de versión en la página web: /)
l0b0
<<<es bash 'aquí cadena'. No creo que aparezca en ningún otro caparazón. (Oh, zshtal vez ...)
rozcietrzewiacz
2
@rozcietrzewiacz: Google para man ksh. Ciertamente fue mencionado allí.
l0b0
3
Adivina cómo bash implementa here-strings y here-docs. sleep 3 <<<"here string" & lsof -p $! | grep 0rsleep 30251 anthony 0r REG 253,0 12 263271 /tmp/sh-thd-7256597168 (deleted)- sí, usa un archivo temporal.
derobert
2

Tienes dos opciones:

  1. Usted recupera los datos una vez (en su ejemplo con getInfo) y los almacena en un archivo como lo hace.

  2. Obtiene los datos cada vez y no los almacena localmente, es decir, llama getInfocada vez

No veo el problema al crear un archivo temporal para evitar el reprocesamiento / recuperación.

Si le preocupa dejar el archivo temporal en algún lugar, siempre puede usarlo trappara asegurarse de eliminarlo en caso de que el script sea eliminado / interrumpido

trap "rm -f $TMPFILE" EXIT HUP INT QUIT TERM

y úselo mktemppara crear un nombre de archivo único para su archivo temporal.

Matteo
fuente
1

En lugar de generar un archivo, construya sentencias de asignación de shell y evalúe esa salida.

for SERVICE in $(myfunc); do
    eval $(getInfo $SERVICE |
               sed -n -e '1/\(.*\) *$/SERV_NAME="\1"/p' \
                   -e '/HOSTNAME/s/^[^=]*=\([^=]*\).*/SERV_HOSTNAME="\1"/p' \
                   -e '/Arguments/^[^:]*:\([^:]*\).*/SERV_ARGS="\1"/p')
    print $SERV_NAME $SEP $SERV_HOSTNAME $SED $SERV_ARGS
done

O si solo desea imprimir la información:

for SERVICE in $(myfunc); do
    getInfo $SERVICE | awk -vsep="$SEP" '
        BEGIN{OFS=sep}
        NR == 1 { sub(/ *$/,""); SERV_NAME=$0 }
        /HOSTNAME/ { split($0, HOST, /=/; SERV_HOSTNAME=HOST[2]; }
        /Arguments/ { split($0, ARGS, /:/; SERV_ARGS }
        END { print SERV_NAME, SERV_HOSTNAME, SERV_ARGS }'
done
Arcege
fuente