Pobierz określoną linię z pliku tekstowego za pomocą skryptu powłoki

101

Próbuję uzyskać określoną linię z pliku tekstowego.

Do tej pory w Internecie widziałem tylko rzeczy takie jak sed (mogę używać tylko sh -not bash lub sed lub czegoś podobnego). Muszę to zrobić tylko przy użyciu podstawowego skryptu powłoki.

cat file | while read line
    do
       #do something
    done

Wiem, jak iterować po liniach, jak pokazano powyżej, ale co, jeśli potrzebuję tylko uzyskać zawartość określonej linii

GangstaGraham
źródło
znasz numer linii?
Mehul Rathod
1
Wtedy będziesz liczyć.
Ignacio Vazquez-Abrams
tak, numer linii to 5 @MehulRathod
GangstaGraham,
3
Dlaczego jest w catporządku, ale sednie jest? To nie ma sensu.
William Pursell,
5
Ponieważ nikt nie może odmówić cat. Och ... uroczy cat!

Odpowiedzi:

204

sed:

sed '5!d' file

awk:

awk 'NR==5' file
Kent
źródło
A co z poleceniem sh, nie mogę używać sed, awk. Powinienem to wyjaśnić w pytaniu.
GangstaGraham,
@GangstaGraham, powiedziałeś, że wiesz, jak iterować po liniach, a co powiesz na dodanie licznika? jeśli licznik osiągnie docelowy numer linii, weź tę linię i przerwij pętlę. czy to pomaga?
Kent
4
@KanagaveluSugumar przeczytaj stronę informacyjną seda. 5!doznacza usuń wszystkie linie z wyjątkiem 5. shell var jest możliwe, potrzebujesz podwójnych cudzysłowów.
Kent
13
Sugerowałbym dodanie innego wariantu: sed -n 5pwydaje się to bardziej logiczne do zapamiętania dla początkujących, ponieważ -noznacza "domyślnie brak wyjścia" i poznacza "drukuj" i nie ma potencjalnie mylącej wzmianki o usuwaniu (kiedy ludzie mówią o plikach, usuwanie linii ma tendencję do znaczy coś innego).
Josip Rodin
1
@JosipRodin masz rację, -n '5p'działa też w przypadku tego problemu. Różnica polega na tym, 5!dże możesz dodać, -iaby zapisać zmianę z powrotem do pliku. jednakże, -n 5pco do tego sed -n '5p' f > f2&& mv f2 fznowu, zgadzam się z pańską opinią na to pytanie.
Kent
21

Zakładając, że linejest to zmienna, która przechowuje wymagany numer linii, jeśli możesz użyć headi tail, to jest całkiem proste:

head -n $line file | tail -1

Jeśli nie, to powinno działać:

x=0
want=5
cat lines | while read line; do
  x=$(( x+1 ))
  if [ $x -eq "$want" ]; then
    echo $line
    break
  fi
done
mikromosy
źródło
To -eqporównanie dotyczy liczb całkowitych, więc potrzebny jest numer wiersza, a nie zawartość wiersza ( $line). Należy to naprawić, definiując np. want=5Przed pętlą, a następnie używając -eqporównania na $want. [przeniesione z odrzuconej edycji]
Josip Rodin
1
@JosipRodin Zrobiłem niezależną sugestię edycji na podstawie twojego komentarza, z którym się zgadzam. Miejmy nadzieję, że tym razem nie zostanie odrzucony.
Victor Zamanian
16

Możesz użyć sed -n 5p file.

Możesz również uzyskać zakres, np sed -n 5,10p file.

Nomas Prime
źródło
11

Najlepsza metoda wykonania

sed '5q;d' file

Ponieważ sedprzestaje czytać wszystkie wiersze po piątej

Zaktualizuj eksperyment pana Rogera Duecka

Zainstalowałem wcanadian-insane (6,6 MB) i porównałem sed -n 1p / usr / share / dict / words i sed '1q; d' / usr / share / dict / words używając polecenia time; pierwsza trwała 0,043 s, druga tylko 0,002 s, więc użycie „q” zdecydowanie poprawia wydajność!

faithonour
źródło
1
Jest to również powszechnie napisane:sed -n 5q
William Pursell
3
Podoba mi się to rozwiązanie, ponieważ sedprzestaje czytać jakiekolwiek wiersze po piątej.
Anthony Geoghegan
1
Zainstalowałem wcanadian-insane (6,6 MB) i porównałem sed -n 1p /usr/share/dict/wordsi sed '1q;d' /usr/share/dict/wordsużywając timepolecenia; pierwsza trwała 0,043 s, druga tylko 0,002 s, więc użycie „q” zdecydowanie poprawia wydajność!
Roger Dueck
5

Jeśli na przykład chcesz uzyskać wiersze od 10 do 20 pliku, możesz użyć każdej z tych dwóch metod:

head -n 20 york.txt | tail -11

lub

sed -n '10,20p' york.txt 

p w powyższym poleceniu oznacza drukowanie.

Oto, co zobaczysz: wprowadź opis obrazu tutaj

Mona Jalal
źródło
2

Standardowym sposobem na to jest użycie narzędzi zewnętrznych. Niedopuszczanie do używania zewnętrznych narzędzi podczas pisania skryptu powłoki jest absurdalne. Jeśli jednak naprawdę nie chcesz używać narzędzi zewnętrznych, możesz wydrukować wiersz 5 z:

i=0; while read line; do test $((++i)) = 5 && echo "$line"; done < input-file

Zwróć uwagę, że spowoduje to wydrukowanie linii logicznej 5. Oznacza to, że jeśli input-filezawiera kontynuacje linii, zostaną one policzone jako pojedynczy wiersz. Możesz zmienić to zachowanie, dodając -rdo polecenia odczytu. (Co jest prawdopodobnie pożądanym zachowaniem).

William Pursell
źródło
1
$((++i))wydaje się być bashizmem; jeśli OP jest ograniczony w korzystaniu z zewnętrznych narzędzi, nie zakładałbym, że będą mieli dostęp do czegoś więcej niż zwykłego/bin/sh
Josip Rodin
@JosipRodin Nie, to funkcja POSIX (ale obsługa ++przyrostów jest specjalnie oznaczona jako opcjonalna).
tripleee
@tripleee nie działa z nowoczesnym myślnikiem jako / bin / sh, więc nie polegałbym na tym.
Josip Rodin
Ale proste obejście, takie jak $((i+=1))działa również w Dash.
tripleee
$(($i+1))to proste obejście, o którym myślałem.
Josip Rodin
1

Równolegle z odpowiedzią Williama Pursella , oto prosta konstrukcja, która powinna działać nawet w oryginalnej powłoce Bourne'a v7 (a więc także w miejscach, w których Bash nie jest dostępny).

i=0
while read line; do
    i=`expr "$i" + 1`
    case $i in 5) echo "$line"; break;; esac
done <file

Zwróć także uwagę na optymalizację breakwyjścia z pętli, gdy otrzymaliśmy szukaną linię.

tripleee
źródło
0

Nie podobała mi się żadna z odpowiedzi.

Oto jak to zrobiłem.

# Convert the file into an array of strings
lines=(`cat "foo.txt"`)

# Print out the lines via array index
echo "${lines[0]}"
echo "${lines[1]}"
echo "${lines[5]}"
cpp_guy_who_does_gfx
źródło
-1

Łatwo z Perlem! Jeśli chcesz pobrać linie 1, 3 i 5 z pliku, powiedz / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,3,5]){print}}' < /etc/passwd
dagelf
źródło
seq 5 | perl -ne 'print if $. ~~ [1, 4, 5]'ale smartmatch jest eksperymentalny i odradza się jego używania
Sorin
Żadne z pozostałych rozwiązań nie jest tak zwięzłe ani nie pozwala na tak dużą elastyczność. (Dlaczego wydaje się, że wszystko, co oszczędza czas i ułatwia życie, jest „zniechęcane” przez „inteligentnych ludzi”, czy wszyscy mamy cały dzień gapić się na ekrany?)
dagelf
-1
line=5; prep=`grep -ne ^ file.txt | grep -e ^$line:`; echo "${prep#$line:}"
Odra
źródło
3
czy mógłbyś chociaż trochę opisać, dlaczego ta praca jest bardziej zrozumiała dla osoby, która zadała pytanie?
ted
Tak więc pierwszy grep wybiera wszystkie wiersze, dodając numery wierszy na ich początku. Następnie drugi grep wybiera konkretną linię, dopasowując numer linii na początku. I na koniec numer linii jest przycinany od początku linii w echu.
Odra
Jest to zarówno złożone, jak i nieefektywne w porównaniu do sed -n 5p, które oczywiście można jeszcze zoptymalizować do czegoś takiegosed -n '5!d;p;q'
tripleee