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


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

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

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.


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ść!


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


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


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


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]}"

-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

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:}"

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