una reemplazar linea leer fichero extraer cadena buscar archivo linux bash sed text-processing

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


Si se trata de una operación de una sola vez y no hay muchas líneas para elegir, puede utilizar el método de selección para seleccionarlas manualmente:

cat test.txt | pick | ...

Se abrirá una pantalla interactiva que le permitirá seleccionar lo que desea.