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"
Luego otorgue permisos de ejecución:
chmod +x lines.sh
Y llámalo como:
./lines.sh '2,4' 'test.txt'
Prueba sed
:
sed -n '2p; 4p' inputFile
-n
le dice a sed
para suprimir la salida, pero para las líneas 2
y 4
, el p
El comando (imprimir) se usa para imprimir estas líneas.
También puede usar rangos, por ejemplo:
sed -n '2,4p' inputFile
Dos versiones puras de Bash. Dado que está buscando soluciones generales y reutilizables, también podría esforzarse un poco en eso. (También, vea la última sección).
Versión 1
Este script absorbe 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 con espacios o comas. Las líneas se imprimen exactamente en el orden en que se dan 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.
Aquí 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.
- Desventaja:necesita leer todo el flujo en la memoria. No apto 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, se permiten rangos:
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 <<< "[email protected]"
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 genial y no lee la transmisión completa en la memoria.
- Con:No se pueden repetir ni reordenar las líneas como en la versión 1. La velocidad no es su punto más fuerte.
Otros pensamientos
Si realmente desea un script general increíble que haga lo que hacen la Versión 1 y la Versión 2, y más, definitivamente debería considerar usar otro lenguaje, por ejemplo, Perl:¡ganará mucho (en particular, velocidad)! podrás tener buenas opciones que harán muchas cosas mucho más geniales. Puede valer la pena a largo plazo, ya que desea un script general y reutilizable. ¡Incluso podría terminar teniendo un script que lea correos electrónicos!
Descargo de responsabilidad. No he revisado a fondo estos scripts... ¡así que tenga cuidado con los errores!