Comando per ottenere l'ennesima riga di STDOUT


210

C'è qualche comando bash che ti permetterà di ottenere l'ennesima riga di STDOUT?

Vale a dire, qualcosa che lo prenderebbe

$ 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

e fare qualcosa di simile

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

Mi rendo conto che questa sarebbe una cattiva pratica quando si scrivono script destinati a essere riutilizzati, MA quando si lavora con la shell giorno per giorno mi sarebbe utile poter filtrare il mio STDOUT in questo modo.

Mi rendo anche conto che questo sarebbe un comando semi-banale da scrivere (buffer STDOUT, restituisce una riga specifica), ma voglio sapere se c'è qualche comando shell standard per farlo che sarebbe disponibile senza che io trascini uno script in posizione.


5
Ho appena votato a favore per l'uso della parolamagic-command
Sevenearths,

Risposte:


332

Utilizzando sed, solo per varietà:

ls -l | sed -n 2p

L'uso di questa alternativa, che sembra più efficiente poiché interrompe la lettura dell'input quando viene stampata la riga richiesta, può generare un SIGPIPE nel processo di alimentazione, che a sua volta può generare un messaggio di errore indesiderato:

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

Ho visto abbastanza spesso che di solito uso il primo (che è più facile da scrivere, comunque), anche se lsnon è un comando che si lamenta quando ottiene SIGPIPE.

Per una gamma di linee:

ls -l | sed -n 2,4p

Per diverse gamme di linee:

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

Cosa significa p? come 2p 3p? Nel senso che capisco che è per la linea 2/3, ma qual è la teoria / comprensione dietro di essa?
Leo Ufimtsev,

4
@LeoUfimtsev: pè l' sedistruzione che stampa le linee identificate dall'intervallo di linee che la precede. Quindi 2,4psignifica stampare le righe 2, 3, 4.
Jonathan Leffler il

3
E nsignifica NON stampare tutte le altre righe
Timo

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

13
+2, ma suggerirei head -n 2 | tail -n 1: le teste e le code moderne tendono ad emettere avvertimenti altrimenti.
Michael Krelin - hacker,

2
Usare due processi per filtrare i dati è un po 'eccessivo (ma, data la potenza delle macchine in questi giorni, probabilmente ce la farebbero). Generalizzare per gestire un intervallo non è del tutto banale (per l'intervallo N..M, si usa head -n M | tail -n M-N+1), e generalizzare per gestire intervalli multipli non è possibile.
Jonathan Leffler,

3
testa convogliata nella coda? orribile. Molteplici modi migliori per farlo.
Camh,

16
Grande cosa sulla riga di comando * nix: un milione e uno di modi per fare tutto e tutti hanno un preferito e odiano tutti gli altri modi ... :-)
Beggs

1
Come stampare un intervallo di linee in un altro modo: $ ls -l | awk '{if (NR>=4 && NR<=64) print}'(può aggiungere più condizioni più facilmente, intervalli multipli, ecc ...)
user10607

41

Alternativa al bel modo testa / coda:

ls -al | awk 'NR==2'

o

ls -al | sed -n '2p'

1
il mio awk è migliore, ma sed è carino.
Michael Krelin - hacker,

Non c'è bisogno di un "if" - vedi la risposta dell'hacker (tranne per la parte sulla ricerca di ogni file sul sistema). ;-)
In pausa fino a nuovo avviso.

cosa significa '2p'?
triturazione del

1
@shredding risposta breve stampa seconda riga; long -> Se utilizzato, solo le righe che corrispondono al modello ricevono il comando dopo l'indirizzo. In breve, se usato con il flag / p, le linee corrispondenti vengono stampate due volte: file sed '/ PATTERN / p'. E, naturalmente, MODELLO è un qualsiasi espressione regolare più
Medhat

e se 2fosse una variabile? Ogni esempio usa virgolette singole, ma con una var hai bisogno di virgolette doppie. Potremmo "programmare" awk in modo che funzioni anche con virgolette singole quando si usa Vars? Esempio line =1; ls | awk 'NR==$line'. So che bashusa virgolette doppie con var.
Timo,

21

Da sed1line :

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

Da awk1line :

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

9

Per amor di completezza ;-)

codice più breve

find / | awk NR==3

vita più breve

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

Non stai scherzando sulla completezza!
In pausa fino a nuovo avviso.

Non lo sono ;-) In realtà, non so perché tutti lo stiano usando ls: la domanda originale riguardava quella di qualcunoSTDOUT , quindi ho pensato che fosse meglio averlo più grande.
Michael Krelin - hacker,

Almeno con GNU awk, l'azione predefinita è { print $0 }, quindi `awk 'NR == 3' è un modo più breve di scrivere lo stesso.
effimero

Probabilmente dovresti aggiungere "; exit" a quell'azione. Non ha senso elaborare il resto delle linee quando non hai intenzione di fare nulla con esse.
Camh,

@camh: vedi la risposta di Jonathan Leffer al riguardo: "può generare un SIGPIPE nel processo di alimentazione, che a sua volta può generare un messaggio di errore indesiderato".
effimero


6

Puoi usare awk:

ls -l | awk 'NR==2'

Aggiornare

Il codice sopra riportato non otterrà ciò che vogliamo a causa di un errore off-by-one: la prima riga del comando ls -l è la riga totale . Per questo, il seguente codice modificato funzionerà:

ls -l | awk 'NR==3'

4

Perl è facilmente disponibile per te?

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

Ovviamente sostituisci il numero che vuoi con 7.


Questa è la mia preferita poiché sembra essere la più semplice da generalizzare a ciò che ero personalmente dopo che è ogni ennesimo, perl -n -e 'if ($.% 7 == 0) {print; exit (0); } '
mat kelcey,

@matkelcey anche tu puoi fare $ awk 'NR % 7' filenameper stampare ogni settima riga del file.
user10607,

3

Un altro poster ha suggerito

ls -l | head -2 | tail -1

ma se conduci la testa nella coda, sembra che tutto fino alla linea N sia processato due volte.

Piping tail in head

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

sarebbe più efficiente?


0

Sì, il modo più efficiente (come già sottolineato da Jonathan Leffler) è usare sed con print ed esci:

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'

L'uso di pipefail e PIPESTATUS sono molto interessanti e questo aggiunge molto a questa pagina.
Mike S,

0

Hmm

sed non ha funzionato nel mio caso. Propongo:

per le righe "dispari" 1,3,5,7 ... ls | awk '0 == (NR + 1)% 2'

per le linee "pari" 2,4,6,8 ls | awk '0 == (NR)% 2'


-1

Per maggiore completezza ..

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

Getta via le linee fino ad arrivare alla seconda, quindi stampa la prima riga dopo. Quindi, stampa la terza riga.

Se è solo la seconda riga ..

ls -l | (read; head -n1)

Metti tutte le letture necessarie.


-1

Ho aggiunto questo nel mio .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
}

E funziona

gdf <file name>

Sono perplesso: cosa c'entra questo con la domanda?
Jonathan Leffler, il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.