Comando para obtener la enésima línea de STDOUT


210

¿Hay algún comando bash que te permita obtener la enésima línea de STDOUT?

Es decir, algo que llevaría esto

$ ls -l
-rw-r--r--@ 1 root  wheel my.txt
-rw-r--r--@ 1 root  wheel files.txt
-rw-r--r--@ 1 root  wheel here.txt

y hacer algo como

$ ls -l | magic-command 2
-rw-r--r--@ 1 root  wheel files.txt

Me doy cuenta de que esto sería una mala práctica al escribir scripts destinados a ser reutilizados, PERO cuando trabaje con el shell día a día, sería útil para mí poder filtrar mi STDOUT de tal manera.

También me doy cuenta de que este sería un comando semi-trivial para escribir (buffer STDOUT, devolver una línea específica), pero quiero saber si hay algún comando de shell estándar para hacerlo que estaría disponible sin que coloque un script en su lugar.


55
Acabo de votar por el uso de la palabramagic-command
Sevenearths

Respuestas:


332

Usando sed, solo por variedad:

ls -l | sed -n 2p

El uso de esta alternativa, que parece más eficiente ya que deja de leer la entrada cuando se imprime la línea requerida, puede generar un SIGPIPE en el proceso de alimentación, que a su vez puede generar un mensaje de error no deseado:

ls -l | sed -n -e '2{p;q}'

Lo he visto con tanta frecuencia que usualmente uso el primero (que de todos modos es más fácil de escribir), aunque lsno es un comando que se queja cuando obtiene SIGPIPE.

Para una gama de líneas:

ls -l | sed -n 2,4p

Para varios rangos de líneas:

ls -l | sed -n -e 2,4p -e 20,30p
ls -l | sed -n -e '2,4p;20,30p'

¿Qué significa la p? como 2p 3p? En el sentido de que entiendo que es para 2/3 líneas, pero ¿cuál es la teoría / comprensión detrás de esto?
Leo Ufimtsev

44
@LeoUfimtsev: pes la seddeclaración que imprime las líneas identificadas por el rango de líneas que la precede. Por 2,4plo tanto, significa imprimir las líneas 2, 3, 4.
Jonathan Leffler

3
Y nsignifica NO imprimir todas las otras líneas
Timo

85
ls -l | head -2 | tail -1

13
+2, pero sugeriría head -n 2 | tail -n 1: las cabezas y las colas modernas tienden a emitir advertencias de lo contrario.
Michael Krelin - hacker

2
El uso de dos procesos para filtrar los datos es un poco exagerado (pero, dada la potencia de las máquinas en estos días, probablemente se las arreglarán). Generalizar para manejar un rango no es del todo trivial (para el rango N..M, usted usa head -n M | tail -n M-N+1), y generalizar para manejar múltiples rangos no es posible.
Jonathan Leffler

3
cabeza entubada en la cola? horrible. Múltiples formas mejores de hacerlo.
camh

16
Lo bueno de línea de comandos * nix: un millón y una maneras de hacer todo y todo el mundo tiene un favorito y un odios todas las otras maneras ... :-)
Beggs

1
Cómo imprimir un rango de líneas de otra manera: $ ls -l | awk '{if (NR>=4 && NR<=64) print}'(puede agregar más condiciones más fácilmente, múltiples rangos, etc.)
user10607

41

Alternativa a la agradable forma de cabeza / cola:

ls -al | awk 'NR==2'

o

ls -al | sed -n '2p'

1
el mío es mejor, pero sed es bueno.
Michael Krelin - hacker

No es necesario un "si": consulte la respuesta del hacker (excepto la parte sobre cómo encontrar cada archivo en el sistema). ;-)
pausa hasta nuevo aviso.

¿Qué significa '2p'?
Destrucción

1
@shredding respuesta corta print segunda línea; largo -> Cuando se usa, solo las líneas que coinciden con el patrón reciben el comando después de la dirección. Brevemente, cuando se usa con el indicador / p, las líneas coincidentes se imprimen dos veces: archivo sed '/ PATTERN / p'. Y, por supuesto, PATTERN es cualquier expresión regular más
Medhat

¿Qué 2pasa si es una variable? Cada ejemplo usa comillas simples, pero con una var necesita comillas dobles. ¿Podríamos "programar" awk para que funcione también con comillas simples al usar vars? Ejemplo line =1; ls | awk 'NR==$line'. Sé que bashusa comillas dobles con vars.
Timo

21

De sed1line :

# print line number 52
sed -n '52p'                 # method 1
sed '52!d'                   # method 2
sed '52q;d'                  # method 3, efficient on large files

Desde awk1line :

# print line number 52
awk 'NR==52'
awk 'NR==52 {print;exit}'          # more efficient on large files

9

En aras de la exhaustividad ;-)

código más corto

find / | awk NR==3

vida más corta

find / | awk 'NR==3 {print $0; exit}'

¡No estás bromeando sobre lo completo!
Pausado hasta nuevo aviso.

No lo estoy ;-) En realidad, no sé por qué todo el mundo está usando ls, la pregunta original era sobre alguien STDOUT, así que pensé que era mejor hacerlo más grande.
Michael Krelin - hacker

Al menos con GNU awk, la acción predeterminada es { print $0 }, entonces 'awk' NR == 3 'es una forma más corta de escribir lo mismo.
ephemient

Probablemente deberías agregar "; salir" a esa acción. No tiene sentido procesar el resto de las líneas cuando no va a hacer nada con ellas.
camh

@camh: Vea la respuesta de Jonathan Leffer sobre eso: "puede generar un SIGPIPE en el proceso de alimentación, que a su vez puede generar un mensaje de error no deseado".
Ephemient


6

Puedes usar awk:

ls -l | awk 'NR==2'

Actualizar

El código anterior no obtendrá lo que queremos debido a un error off-by-one: la primera línea del comando ls -l es la línea total . Para eso, funcionará el siguiente código revisado:

ls -l | awk 'NR==3'

4

¿Perl está fácilmente disponible para usted?

$ perl -n -e 'if ($. == 7) { print; exit(0); }'

Obviamente, sustituya el número que desee por 7.


Este es mi favorito, ya que parece ser el más fácil de generalizar a lo que era personalmente después de lo cual es cada enésimo, perl -n -e 'if ($.% 7 == 0) {print; salida (0); } '
mat kelcey

@matkelcey también puede hacerlo $ awk 'NR % 7' filenamepara imprimir cada 7ma línea en el archivo.
user10607

3

Otro póster sugerido

ls -l | head -2 | tail -1

pero si conecta la cabeza hacia la cola, parece que todo hasta la línea N se procesa dos veces.

Colocando la cola en la cabeza

ls -l | tail -n +2 | head -n1

sería más eficiente?


0

Sí, la forma más eficiente (como ya lo señaló Jonathan Leffler) es usar sed con print & quit:

set -o pipefail                        # cf. help set
time -p ls -l | sed -n -e '2{p;q;}'    # only print the second line & quit (on Mac OS X)
echo "$?: ${PIPESTATUS[*]}"            # cf. man bash | less -p 'PIPESTATUS'

El uso de pipefail y PIPESTATUS es muy interesante y esto agrega mucho a esta página.
Mike S

0

Hmm

sed no funcionó en mi caso. Yo propongo:

para líneas "impares" 1,3,5,7 ... ls | awk '0 == (NR + 1)% 2'

para líneas "pares" 2,4,6,8 ls | awk '0 == (NR)% 2'


-1

Para más integridad ..

ls -l | (for ((x=0;x<2;x++)) ; do read ; done ; head -n1)

Deseche las líneas hasta llegar a la segunda, luego imprima la primera línea después de eso. Entonces, imprime la tercera línea.

Si es solo la segunda línea ...

ls -l | (read; head -n1)

Ponga tantas 'lecturas como sea necesario.


-1

Agregué esto en mi .bash_profile

function gdf(){
  DELETE_FILE_PATH=$(git log --diff-filter=D --summary | grep delete | grep $1 | awk '{print $4}')
  SHA=$(git log --all -- $DELETE_FILE_PATH | grep commit | head -n 1 | awk '{print $2}')
  git show $SHA -- $DELETE_FILE_PATH
}

Y funciona

gdf <file name>

Estoy perplejo: ¿qué tiene esto que ver con la pregunta?
Jonathan Leffler
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.