linux - linea - sed reemplazar
¿Cómo seleccionar varias líneas de un archivo o de un conducto en un script? (7)
Bueno, siempre que:
- su archivo es lo suficientemente pequeño
- no tiene ningún punto y coma (u otro carácter específico de su elección) en el archivo
- no te importa usar múltiples tuberías
podrías usar algo como:
cat test.txt |tr "//n" ";"|cut -d'';'' -f2,4|tr ";" "//n"
Donde -f2,4 indica las líneas que desea extraer
Me gustaría tener un script, llamado lines.sh
que pueda canalizar datos para seleccionar una serie de líneas.
Por ejemplo, si tuviera el siguiente archivo:
test.txt
a
b
c
d
Entonces podría correr:
cat test.txt | lines 2,4
y saldría
b
d
Estoy usando zsh, pero preferiría una solución bash si es posible.
Dos versiones de Bash puro. Ya que está buscando soluciones generales y reutilizables, también podría poner un poco de esfuerzo en eso. (También, vea la última sección).
Versión 1
Esta secuencia de comandos sorbe todo el stdin en una matriz (usando mapfile
, por lo que es bastante eficiente) y luego imprime las líneas especificadas en sus argumentos. Los rangos son válidos, por ejemplo,
1-4 # for lines 1, 2, 3 and 4
3- # for everything from line 3 till the end of the file
Puede separarlos por espacios o comas. Las líneas se imprimen exactamente en el orden en que se presentan los argumentos:
lines 1 1,2,4,1-3,4- 1
imprimirá la línea 1 dos veces, luego la línea 2, luego la línea 4, luego las líneas 1, 2 y 3, luego todo desde la línea 4 hasta el final, y finalmente, la línea 1 nuevamente.
Aqui tienes:
#!/bin/bash
lines=()
# Slurp stdin in array
mapfile -O1 -t lines
# Arguments:
IFS='', '' read -ra args <<< "$*"
for arg in "${args[@]}"; do
if [[ $arg = +([[:digit:]]) ]]; then
arg=$arg-$arg
fi
if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
((from=10#${BASH_REMATCH[1]}))
((to=10#${BASH_REMATCH[2]:-$((${#lines[@]}))}))
((from==0)) && from=1
((to>=${#lines[@]})) && to=${#lines[@]}
((from<=to)) || printf >&2 ''Argument %d-%d: lines not in increasing order'' "$from" "$to"
for((i=from;i<=to;++i)); do
printf ''%s/n'' "${lines[i]}"
done
else
printf >&2 "Error in argument /`%s''./n" "$arg"
fi
done
- Pro: es realmente genial.
- Con: Necesita leer todo el flujo en la memoria. No es adecuado para flujos infinitos.
Versión 2
Esta versión aborda el problema anterior de flujos infinitos. Pero perderá la capacidad de repetir y reordenar líneas.
Lo mismo, los rangos están permitidos:
lines 1 1,4-6 9-
imprimirá las líneas 1, 4, 5, 6, 9 y todo hasta el final. Si el conjunto de líneas está limitado, sale tan pronto como se lee la última línea.
#!/bin/bash
lines=()
tillend=0
maxline=0
# Process arguments
IFS='', '' read -ra args <<< "$@"
for arg in "${args[@]}"; do
if [[ $arg = +([[:digit:]]) ]]; then
arg=$arg-$arg
fi
if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
((from=10#${BASH_REMATCH[1]}))
((from==0)) && from=1
((tillend && from>=tillend)) && continue
if [[ -z ${BASH_REMATCH[2]} ]]; then
tillend=$from
continue
fi
((to=10#${BASH_REMATCH[2]}))
if ((from>to)); then
printf >&2 "Invalid lines order: %s/n" "$arg"
exit 1
fi
((maxline<to)) && maxline=$to
for ((i=from;i<=to;++i)); do
lines[i]=1
done
else
printf >&2 "Invalid argument /`%s''/n" "$arg"
exit 1
fi
done
# If nothing to read, exit
((tillend==0 && ${#lines[@]}==0)) && exit
# Now read stdin
linenb=0
while IFS= read -r line; do
((++linenb))
((tillend==0 && maxline && linenb>maxline)) && exit
if [[ ${lines[linenb]} ]] || ((tillend && linenb>=tillend)); then
printf ''%s/n'' "$line"
fi
done
- Pro: es realmente genial y no lee todo el flujo en la memoria.
- Con: No puede repetir o reordenar líneas como la Versión 1. La velocidad no es su punto más fuerte.
Pensamientos adicionales
Si realmente quieres una secuencia de comandos general impresionante que hace lo que hace la versión 1 y la versión 2, y más, definitivamente debes considerar el uso de otro idioma, por ejemplo, Perl: ¡ganarás mucho (en particular velocidad)! podrás tener buenas opciones que harán muchas cosas mucho más geniales. A largo plazo, valdrá la pena, ya que quiere un guión general y reutilizable. ¡Incluso podría terminar teniendo un script que lea correos electrónicos!
Renuncia. No he revisado a fondo estos scripts ... ¡así que ten cuidado con los errores!
Prueba sed
:
sed -n ''2p; 4p'' inputFile
-n
le dice a sed
que suprima la salida, pero para las líneas 2
y 4
, el comando p
(imprimir) se usa para imprimir estas líneas.
También puede usar rangos, por ejemplo:
sed -n ''2,4p'' inputFile
Prueba esto :
file=$1
for var in "$@" //var is all line numbers
do
sed -n "${var}p" $file
done
Creé un script con 1 parámetro de archivo y un número ilimitado de parámetros para los números de línea. Lo llamarías así:
lines txt 2 3 4...etc
Puedes usar este awk:
awk -v s=''2,4'' ''BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b'' file
two
four
A través de un script separado lines.sh
:
#!/bin/bash
awk -v s="$1" ''BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b'' "$2"
A continuación, otorgue permisos de ejecución:
chmod +x lines.sh
Y llámalo como:
./lines.sh ''2,4'' ''test.txt''
Solución rápida para tu amigo Entrada:
test.txt
a
b
c
d
e
f
g
h
i
j
test.sh
lines (){
sed -n "$( echo "$@" | sed ''s/[0-9]/+/&p;/g'')"
}
cat 1.txt | lines 1 5 10
O si quieres tener tus lines
como script:
lines.sh
IFS='','' read -a lines <<< "$1"; sed -n "$( echo "${lines[@]}" | sed ''s/[0-9]/+/&p;/g'')" "$2"
./lines.sh 1,5,10 test.txt
Salida en ambos casos:
a
e
j