Polecenie, aby uzyskać n-ty wiersz STDOUT

210

Czy jest jakieś polecenie bash, które pozwoli ci uzyskać n-ty wiersz STDOUT?

To znaczy, coś, co by to wzięło

$ 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

i zrób coś takiego

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

Zdaję sobie sprawę, że byłoby to złą praktyką podczas pisania skryptów przeznaczonych do ponownego użycia, ALE podczas codziennej pracy z powłoką przydałaby mi się możliwość filtrowania mojego STDOUT w taki sposób.

Zdaję sobie również sprawę, że byłoby to trywialne polecenie do napisania (buforuj STDOUT, zwróć konkretną linię), ale chcę wiedzieć, czy istnieje jakieś standardowe polecenie powłoki, które by to zrobiło, które nie byłoby dostępne bez umieszczania skryptu na swoim miejscu.

Alan Storm
źródło
5
Właśnie głosowałem za użyciem słowamagic-command
Sevenearths,

Odpowiedzi:

332

Używanie sedtylko dla odmiany:

ls -l | sed -n 2p

Użycie tej alternatywy, która wygląda bardziej wydajnie, ponieważ przestaje czytać dane wejściowe po wydrukowaniu wymaganego wiersza, może wygenerować SIGPIPE w procesie podawania, co z kolei może wygenerować niepożądany komunikat o błędzie:

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

Widziałem to tak często, że zwykle używam pierwszego (co i tak jest łatwiejsze do pisania), chociaż lsnie jest to polecenie, które narzeka, gdy otrzymuje SIGPIPE.

Dla szeregu linii:

ls -l | sed -n 2,4p

Dla kilku zakresów linii:

ls -l | sed -n -e 2,4p -e 20,30p
ls -l | sed -n -e '2,4p;20,30p'
Jonathan Leffler
źródło
Co oznacza p? jak 2p 3p? W tym sensie rozumiem, że dotyczy linii 2/3, ale jaka jest za tym teoria / zrozumienie?
Leo Ufimcew
4
@LeoUfimtsev: pto sedinstrukcja, która drukuje linie identyfikowane przez zakres linii, które ją poprzedzają. Oznacza 2,4pto, że drukuje linie 2, 3, 4.
Jonathan Leffler,
3
I noznacza NIE drukowanie wszystkich pozostałych wierszy
Timo
85
ls -l | head -2 | tail -1
tłum
źródło
13
+2, ale sugerowałbym head -n 2 | tail -n 1- nowoczesne głowy i ogony zwykle wydają ostrzeżenia w przeciwnym razie.
Michael Krelin - haker
2
Używanie dwóch procesów do filtrowania danych to trochę przesada (ale biorąc pod uwagę moc maszyn w dzisiejszych czasach, prawdopodobnie by sobie poradziły). Generalizacja do obsługi jednego zakresu nie jest całkowicie trywialna (dla zakresu N..M, używasz head -n M | tail -n M-N+1), a generalizacja do obsługi wielu zakresów nie jest możliwa.
Jonathan Leffler,
3
Głowa wpięta w ogon? okropny. Wiele lepszych sposobów na zrobienie tego.
camh
16
Świetna rzecz w wierszu poleceń * nix: milion i jeden sposób na zrobienie wszystkiego, każdy ma swoje ulubione i nienawidzi wszystkich innych sposobów: :-)
zaczyna się
1
Jak wydrukować zakres linii w inny sposób: $ ls -l | awk '{if (NR>=4 && NR<=64) print}'(można łatwiej dodać więcej warunków, wiele zakresów itp.)
user10607
41

Alternatywa dla ładnego sposobu głowa / ogon:

ls -al | awk 'NR==2'

lub

ls -al | sed -n '2p'
ChristopheD
źródło
1
mój awk jest lepszy, ale sed jest fajny.
Michael Krelin - haker
Nie ma potrzeby „jeśli” - patrz odpowiedź hakera (z wyjątkiem części dotyczącej znalezienia każdego pliku w systemie). ;-)
Wstrzymano do odwołania.
co to '2p'oznacza?
niszczenie
1
@ shredding krótka odpowiedź wydrukuj drugą linię; long -> Gdy jest używany, tylko linie pasujące do wzorca otrzymują polecenie po adresie. W skrócie, w przypadku użycia z flagą / p pasujące linie są drukowane dwukrotnie: plik sed / PATTERN / p. I oczywiście WZÓR jest jakimkolwiek wyrażeniem regularnym więcej
Medhat
co jeśli 2jest zmienną? Każdy przykład używa pojedynczych cudzysłowów, ale w przypadku zmiennej var potrzebujesz podwójnych cudzysłowów. Czy możemy „zaprogramować” awk do pracy również z pojedynczymi cudzysłowami podczas korzystania z vars? Przykład line =1; ls | awk 'NR==$line'. Wiem, że bashużywa podwójnych cudzysłowów z zmiennymi.
Timo
21

Od sed1line :

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

Z awk1line :

# print line number 52
awk 'NR==52'
awk 'NR==52 {print;exit}'          # more efficient on large files
Mark Edgar
źródło
9

Dla kompletności ;-)

krótszy kod

find / | awk NR==3

krótsze życie

find / | awk 'NR==3 {print $0; exit}'
Michael Krelin - haker
źródło
Nie żartujesz z kompletności!
Wstrzymano do odwołania.
Nie jestem ;-) Właściwie nie wiem, dlaczego wszyscy używają ls- oryginalne pytanie dotyczyło czyjegoś pytania STDOUT, więc pomyślałem, że lepiej jest je powiększyć.
Michael Krelin - haker
Przynajmniej w GNU awk domyślną akcją jest { print $0 }, więc `awk 'NR == 3' jest krótszym sposobem na napisanie tego samego.
ephemient
Prawdopodobnie powinieneś dodać „; exit” do tej akcji. Nie ma sensu przetwarzać reszty linii, jeśli nie zamierzasz nic z nimi zrobić.
camh
@camh: Zobacz odpowiedź Jonathana Leffera na ten temat: „może wygenerować SIGPIPE w procesie karmienia, co z kolei może wygenerować niechciany komunikat o błędzie”.
ephemient
7

Wypróbuj tę sedwersję:

ls -l | sed '2 ! d'

Mówi „usuń wszystkie wiersze, które nie są drugim”.

Wstrzymano do odwołania.
źródło
6

Możesz użyć awk:

ls -l | awk 'NR==2'

Aktualizacja

Powyższy kod nie otrzyma tego, czego chcemy z powodu błędu off-by-one: pierwszy wiersz polecenia ls -l jest wierszem całkowitym . W tym celu zadziała następujący poprawiony kod:

ls -l | awk 'NR==3'
Hai Vu
źródło
4

Czy Perl jest łatwo dostępny?

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

Oczywiście zamień 7 na dowolną liczbę.

karma dla kotów
źródło
Jest to moja ulubiona, ponieważ wydaje się, że najłatwiej jest ją uogólnić na to, co ja osobiście, po czym co n, perl -n -e 'if ($.% 7 == 0) {print; wyjście (0); } '
mat kelcey
@matkelcey również możesz zrobić, $ awk 'NR % 7' filenameaby wydrukować co 7 linię w pliku.
user10607
3

Sugerowany inny plakat

ls -l | head -2 | tail -1

ale jeśli włożysz głowę w ogon, wygląda na to, że wszystko do linii N jest przetwarzane dwukrotnie.

Wbijając ogon w głowę

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

byłby bardziej wydajny?

nikt
źródło
0

Tak, najbardziej wydajnym sposobem (jak już wskazał Jonathan Leffler) jest użycie sed z 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'

źródło
Korzystanie z pipefail i PIPESTATUS jest bardzo interesujące, a to dodaje wiele do tej strony.
Mike S
0

Hmm

sed nie działał w moim przypadku. Proponuję:

dla linii „nieparzystych” 1,3,5,7 ... ls | awk '0 == (NR + 1)% 2'

dla linii „parzystych” 2,4,6,8 ls | awk '0 == (NR)% 2'

stan
źródło
-1

Więcej kompletności ..

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

Wyrzucaj linie, aż dojdziesz do drugiej, a następnie wydrukuj pierwszą linię. Tak więc drukuje trzecią linię.

Jeśli to tylko druga linia ...

ls -l | (read; head -n1)

Umieść tyle lektur, ile potrzeba.

Shizzmo
źródło
-1

Dodałem to do mojego .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
}

I to działa

gdf <file name>
Joel AZEMAR
źródło
Jestem zdziwiony - co to ma wspólnego z pytaniem?
Jonathan Leffler