Tengo un archivo foo.txt
test
qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
ewq
qwe
a
df
fa
vas
fg
fasdf
eqw
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
eqw
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
ewq
sdtjstsa
sdghysdmks
aadfbgns,
asfhytewat
bafg
q4t
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
ewq
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t
ewq
Quiero imprimir todas las líneas entre qwe
y ewq
en un archivo separado.
Esto es lo que tengo hasta ahora:
#!/bin/bash
filename="foo.txt"
#While loop to read line by line
while read -r line
do
readLine=$line
#If the line starts with ST then echo the line
if [[ $readLine = qwe* ]] ; then
echo "$readLine"
read line
readLine=$line
if [[ $readLine = ewq* ]] ; then
echo "$readLine"
fi
fi
done < "$filename"
Necesitas hacer algunos cambios en tu script (sin ningún orden en particular):
IFS=
antes de read
para evitar la eliminación de espacios iniciales y finales.$linea
no se cambia en ningún sitio, no hay necesidad de la variable readLine
.Con esos cambios, el script se convierte en:
#!/bin/bash
filename="foo.txt"
#While loop to read line by line
while IFS= read -r line; do
#If the line starts with ST then set var to yes.
if [[ $line == qwe* ]] ; then
printline="yes"
# Just t make each line start very clear, remove in use.
echo "----------------------->>"
fi
# If variable is yes, print the line.
if [[ $printline == "yes" ]] ; then
echo "$line"
fi
#If the line starts with ST then set var to no.
if [[ $line == ewq* ]] ; then
printline="no"
# Just to make each line end very clear, remove in use.
echo "----------------------------<<"
fi
done < "$filename"
Que podría condensarse de esta manera:
#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
[[ $line == qwe* ]] && printline="yes"
[[ $printline == "yes" ]] && echo "$line"
[[ $line == ewq* ]] && printline="no"
done < "$filename"
Que imprimirá las líneas de inicio y final (inclusive).
Si no es necesario imprimirlas, intercambie las pruebas de inicio y fin:
#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
[[ $line == ewq* ]] && printline="no"
[[ $printline == "yes" ]] && echo "$line"
[[ $line == qwe* ]] && printline="yes"
done < "$filename"
Sin embargo, sería bastante mejor (si tienes bash versión 4.0 o mejor) usar readarray
y hacer un bucle con los elementos del array:
#!/bin/dash
filename="infile"
readarray -t lines < "$filename"
for line in "${lines[@]}"; do
[[ $line == ewq* ]] && printline="no"
[[ $printline == "yes" ]] && echo "$line"
[[ $line == qwe* ]] && printline="yes"
done
Eso evitará la mayoría de los problemas de usar read
.
Por supuesto, podrías usar la línea recomendada (en los comentarios; Gracias, @costas) sed
para obtener sólo las líneas a procesar:
#!/bin/bash
filename="foo.txt"
readarray -t lines <<< "$(sed -n '/^qwe.*/,/^ewq.*/p' "$filename")"
for line in "${lines[@]}"; do
: # Do all your additional processing here, with a clean input.
done
Como ha señalado @Costas, la herramienta correcta para este trabajo es sed
:
sed '/qwe/,/ewq/ w other.file' foo.txt
Puede que se necesite otro procesamiento en las líneas a imprimir. Eso'está bien; simplemente hazlo así:
sed -e '/qwe/,/ewq/{w other.file' -e 'other processing;}' foo.txt
(Por supuesto, "otro procesamiento" no es un verdadero comando sed
.) El anterior es el patrón a utilizar si usted necesita hacer su procesamiento después de imprimir la línea. Si desea hacer algún otro procesamiento y luego imprimir una versión modificada de la línea (lo que parece más probable), usaría:
sed -e '/qwe/,/ewq/{processing;w other.file' -e '}' foo.txt
(Tenga en cuenta que es necesario poner el }
en su propio argumento, de lo contrario se interpretará como parte del nombre otro.archivo
).
Usted (el OP) no ha'indicado qué "otro procesamiento" tiene que hacer en las líneas, o podría ser más específico. Pero cualquiera que sea ese procesamiento, definitivamente puedes hacerlo en sed
-o si eso se vuelve demasiado difícil de manejar, podrías hacerlo en awk
con muy pocos cambios en el código anterior:
awk '/qwe/,/ewq/ { print > "other.file" }' foo.txt
Entonces, tienes todo el poder del lenguaje de programación awk
a tu disposición para procesar las líneas antes de ejecutar la sentencia print
. Y por supuesto awk
(y sed
) están diseñados para procesar texto, a diferencia de bash
.
qwe(){ printf %s\\n "$1"; }
ewq(){ :; }
IFS= ### prep the loop, only IFS= once
while read -r in
do case $in in
(qwe|ewq)
set "$in"
;;
("$processing"?)
"$process"
esac
"$1" "$in"
done
Esa'es una forma muy lenta de hacerlo. Con un GNU grep
y un infile
normal:
IFS=
while grep -xm1 qwe
do while read -r in &&
[ ewq != "$in" ]
do printf %s\\n "$in"
: some processing
done
done <infile
...al menos optimizaría la mitad de las lecturas ineficientes...
sed -ne '/^qwe$/,/^ewq$/H;$!{/^qwe$/!d;}' \
-e "x;s/'"'/&\\&&/g;s/\n/'"' '/g" \
-e "s/\(.*\) .e.*/p '\1/p" <input |
sh -c 'p(){ printf %s\\n "$@"
for l do : process "$l"
done
}; . /dev/fd/0'
Y eso evitaría las ineficiencias de read
por completo para la mayoría de sh
's por ahí, aunque tiene que imprimir la salida dos veces - una vez citado a sh
y una vez sin citar a stdout. Funciona de forma diferente porque el comando .
tiende a leer la entrada por bloques en lugar de por bytes en la mayoría de las implementaciones. Aún así, evita ewq - qwe por completo, y funcionaría para la entrada de flujo - como un FIFO.
qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
qwe
a
df
fa
vas
fg
fasdf
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t