Variables reiniciar después del bucle de lectura while que se lee desde una tubería
-
26-10-2019 - |
Pregunta
initiate () {
read -p "Location(s) to look for .bsp files in? " loc
find $loc -name "*.bsp" | while read
do
if [ -f "$loc.bz2" ]
then
continue
else
filcount=$[$filcount+1]
bzip $loc
fi
if [ "$scan" == "1" ]; then bzipint $loc
fi
echo $filcount #Correct counting
echo $zipcount #Correct counting
echo $scacount #Correct counting
echo $valid #Equal to 1
done
echo $filcount #Reset to 0
echo $zipcount #Reset to 0
echo $scacount #Reset to 0
echo $valid #Still equal to 1
}
Estoy escribiendo un script de shell bash para usar bzip2
Para encender todo .bsp
archivos dentro de un directorio. En este script tengo varias variables para contar totales (archivos, zips exitosos, escaneos de integridad exitosos), sin embargo, parece que me he encontrado con un problema.
Cuando find $loc -name "*.bsp"
se queda sin archivos para dar el while read
y while read
sale, sale $filcount
, $zipcount
y $scacount
(todos los cuales se cambian (aumentan) en el interior initiate ()
, bzip ()
(que se llama durante initiate ()
) o bzipint ()
(que también se llama en initiate ()
).
Para probar si tiene algo que ver con las variables que cambian dentro initiate ()
u otras funciones a las que se accede desde él, utilicé Echo $valid
, que se define fuera de initiate ()
(me gusta $filcount
, $zipcount
, etc.), pero no se cambia de otra función dentro initiate ()
o dentro initiate ()
sí mismo.
Suficientemente interesante, $valid
no se reinicia a 0 como las otras variables dentro del inicio.
¿Alguien puede decirme por qué mis variables se reinician mágicamente cuando se lee?
Solución
Ayer me encontré con este problema.
El problema es que estás haciendo find $loc -name "*.bsp" | while read
. Porque esto involucra una tubería, el while read
Loop no se puede ejecutar en el mismo proceso de bash que el resto de su script; Bash tiene que generar un subproceso para que pueda conectar el stdout de find
al stdin del while
círculo.
Todo esto es muy inteligente, pero significa que las variables establecidas en el bucle no se pueden ver después del bucle, lo que derrotó totalmente a todo el propósito del while
bucle que estaba escribiendo.
Puede intentar alimentar la entrada al bucle sin usar una tubería, o obtener salida del bucle sin usar variables. Terminé con una horrible abominación que involucraba tanto la escritura en un archivo temporal como envolviendo todo el bucle en $(...)
, al igual que:
var="$(producer | while read line; do
...
echo "${something}"
done)"
Lo que me consiguió VAR en todas las cosas que se habían hecho eco desde el bucle. Probablemente arruiné la sintaxis de ese ejemplo; No tengo el código que escribí a Handy en este momento.
Otros consejos
Si usas Bash
while read
do
if [ -f "$REPLY.bz2" ]
then
continue
else
filcount=$[$filcount+1]
bzip $REPLY
fi
if [ "$scan" == "1" ]; then bzipint $REPLY
fi
echo $filcount #Correct counting
echo $zipcount #Correct counting
echo $scacount #Correct counting
echo $valid #Equal to 1
done < <(find $loc -name "*.bsp")
A Resumir opciones por usando read
Al final de [el equivalente conceptual de] una tubería En conchas tipo Posix:
Para recapitular: en bash de forma predeterminada y en shells estrictamente compatibles con Posix siempre, Todos los comandos en una tubería se ejecutan en un subshell, asi que Las variables que crean o modifican no serán visibles para el Actual caparazón (No existirá después de que termine la tubería).
El seguimiento cubiertas bash
, ksh
, zsh
, y sh
([en su mayoría] conchas de solo características de Posix como dash
) y shows formas de evitar la creación de una subshell para preservar las variables creadas / modificadas por read
.
Si no se da un número de versión mínimo, suponga que incluso las versiones "bastante antiguas" lo admiten (las características en cuestión han existido durante mucho tiempo, pero no sé específicamente cuándo se introdujeron.
Tenga en cuenta que como una alternativa [compatible con POSIX] a las soluciones a continuación Siempre puede capturar la salida de un comando en un archivo [temporal] y luego alimentarlo a read
como < file
, que también evita subshells.
ksh
, y zsh
: No se necesita cambio de solución/configuración en absoluto:
los read
incorporado por defecto corre en el Actual cáscara cuando se usa como el ultimo comando en la tubería.
Aparentemente, ksh
y zsh
por defecto correr ningún comando en el ultimo etapa de una tubería en el Actual caparazón.
Observado en ksh 93u+
y zsh 5.0.5
.
Si sabe específicamente en qué versión se introdujo esta función, avíseme.
#!/usr/bin/env ksh
#!/usr/bin/env zsh
out= # initialize output variable
# Pipe multiple lines to the `while` loop and collect the values in the output variable.
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
bash 4.2+
: utilizar el lastpipe
opción de shell
En Bash, versión 4.2 o superior, encendiendo la opción de shell lastpipe
hace que el último segmento de la tubería se ejecute en el Actual shell, permitiendo que la lectura cree variables visibles para el shell actual.
#!/usr/bin/env bash
shopt -s lastpipe # bash 4.2+: make the last pipeline command run in *current* shell
out=
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
bash
, ksh
, zsh
: usar sustitución de procesos
Hablando libremente, una sustitución del proceso es una forma de tener una actuación de salida de comando como un archivo temporal.
out=
while read -r var; do
out+="$var/"
done < <(printf '%s\n' one two three) # <(...) is the process substitution
echo "$out" # -> 'one/two/three'
bash
, ksh
, zsh
: utilizar una aquí con un sustitución de comandos
out=
while read -r var; do
out+="$var/"
done <<< "$(printf '%s\n' one two three)" # <<< is the here-string operator
echo "$out" # -> 'one/two/three'
Tenga en cuenta la necesidad de cotizar doble la sustitución del comando para proteger su salida de expansiones de concha.
Solución compatible con Posix (sh
): utilizar una aquí-documento con un sustitución de comandos
#!/bin/sh
out=
while read -r var; do
out="$out$var/"
done <<EOF # <<EOF ... EOF is the here-doc
$(printf '%s\n' one two three)
EOF
echo "$out" # -> 'one/two/three'
Tenga en cuenta que, por defecto, debe colocar el delimitador final - EOF
, en este caso, al comienzo de la línea, y que ningún personaje debe seguirlo.