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.

Alan Storm
fuente
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'
Jonathan Leffler
fuente
¿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
multitud
fuente
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'
ChristopheD
fuente
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
Mark Edgar
fuente
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}'
Michael Krelin - hacker
fuente
¡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
7

Prueba esta sedversión:

ls -l | sed '2 ! d'

Dice "eliminar todas las líneas que no son la segunda".

Pausado hasta nuevo aviso.
fuente
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'
Hai Vu
fuente
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.

comida de gato
fuente
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?

nadie
fuente
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'

fuente
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'

Stan
fuente
-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.

Shizzmo
fuente
-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>
Joel AZEMAR
fuente
Estoy perplejo: ¿qué tiene esto que ver con la pregunta?
Jonathan Leffler