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.


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'

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

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'

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

9

Dla kompletności ;-)

krótszy kod

find / | awk NR==3

krótsze życie

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

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


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'

4

Czy Perl jest łatwo dostępny?

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

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


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?


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'

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'


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


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

Jestem zdziwiony - co to ma wspólnego z pytaniem?
Jonathan Leffler
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.