Jak znaleźć ostatnie pole za pomocą „cut”

310

Bez korzystania sedlub awk, tylko cut , jak mogę dostać ostatnie pole, gdy liczba pól są nieznane lub zmiana z każdym wierszu?

noobcoder
źródło
8
Czy jesteś zakochany w cutrozkazie :)? dlaczego nie jakieś inne polecenia Linuksa?
Jayesh Bhoi
7
Bez sedlub awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
Jordan
3
możliwy duplikat, jak podzielić ciąg w skorupce i uzyskać ostatnie pole
Amir Ali Akbari
4
@MestreLion Wiele razy ludzie czytają pytanie, aby znaleźć rozwiązanie problemu. Ten zaczyna się od fałszywej przesłanki, która cutobsługuje coś, czego nie ma. Ale pomyślałem, że to przydatne, ponieważ zmusza czytelnika do rozważenia kodu, który jest łatwiejszy do naśladowania. Chciałem szybki, prosty sposób użycia cut, bez konieczności korzystania z wielu składnie dla awk, grep, seditp revrzeczą wystarczyły; bardzo elegancki i coś, czego nigdy nie rozważałem (nawet jeśli jest niezgrabny w innych sytuacjach). Lubiłem też czytać inne podejścia z innych odpowiedzi.
Beejor
3
Przyszedł tutaj prawdziwy problem: chcę znaleźć wszystkie różne rozszerzenia plików w drzewie źródłowym, aby zaktualizować plik .gitattributes. Taka find | cut -d. -f<last>jest naturalna skłonność
studog

Odpowiedzi:

679

Możesz spróbować czegoś takiego:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

Wyjaśnienie

  • rev zmienia się na „maps.google.com” moc.elgoog.spam
  • cut używa kropki (tzn. „.”) jako separatora i wybiera pierwsze pole, którym jest moc
  • wreszcie odwracamy to ponownie, aby uzyskać com
zedfoxus
źródło
6
Nie używa tylko, cutale jest bez sedlub awk. Więc co sądzą OP?
Jayesh Bhoi
7
@tom OP zadał więcej pytań niż tylko to w ciągu ostatnich kilku godzin. Na podstawie naszych interakcji z OP wiemy, że awk / sed / etc. nie są dozwolone w swojej pracy domowej, ale nie ma odniesienia do rew. Warto było
spróbować
4
@zfus Rozumiem. Może revpotem przykleić kolejną .
tom
17
podwójny revświetny ideał!
Ford Guo
6
Niesamowite, proste, idealne, dziękuję również za wyjaśnienia - za mało ludzi tłumaczy każdy krok długim łańcuchem poleceń
Pete
128

Użyj rozszerzenia parametru. Jest to o wiele bardziej wydajne niż jakiekolwiek zewnętrzne polecenie cut(lub grep) włączone.

data=foo,bar,baz,qux
last=${data##*,}

Zobacz BashFAQ # 100, aby zapoznać się z wprowadzaniem natywnych operacji na łańcuchach w bash.

Charles Duffy
źródło
3
@ErwinWessels: Ponieważ bash jest naprawdę wolny. Użyj bash do uruchamiania potoków, a nie do masowego przetwarzania danych. Mam na myśli, że jest to świetne, jeśli masz jeden wiersz tekstu w zmiennej powłoki lub jeśli chcesz zrobić, while IFS= read -ra array_var; do :;done <(cmd)aby przetworzyć kilka wierszy. Ale w przypadku dużego pliku rev ​​| cut | rev jest prawdopodobnie szybszy! (I oczywiście awk będzie szybszy.)
Peter Cordes,
2
@PeterCordes, awk będzie szybszy dla dużego pliku, jasne, ale potrzeba sporo wkładu, aby pokonać stałe koszty uruchomienia. (Istnieją również powłoki - jak ksh93 - o wydajności zbliżonej do awk, gdzie składnia podana w tej odpowiedzi pozostaje poprawna; bash jest wyjątkowo powolny, ale nie jest nawet bliski jedynej dostępnej opcji).
Charles Duffy,
1
Dzięki @PeterCordes; jak zwykle myślę, że każde narzędzie ma swoje zastosowania.
Erwin Wessels,
1
Jest to zdecydowanie najszybszy i najbardziej zwięzły sposób przycięcia pojedynczej zmiennej w bashskrypcie (zakładając, że już używasz bashskryptu). Nie musisz dzwonić na nic zewnętrznego.
Ken Sharp
1
@Balmipour ... jednak rev jest specyficzne cokolwiek OS używasz że zapewnia to - to nie jest ujednolicone we wszystkich systemach UNIX. Zobacz listę rozdziałów w sekcji POSIX na temat poleceń i programów narzędziowych - jej tam nie ma. I tak naprawdę nie${var##prefix_pattern} jest specyficzny dla bash; jest w standardzie sh POSIX , patrz koniec rozdziału 2.6.2 (połączony), więc w przeciwieństwie do tego, zawsze jest dostępny w dowolnej zgodnej powłoce. rev
Charles Duffy,
89

Nie można tego zrobić za pomocą just cut. Oto sposób użycia grep:

grep -o '[^,]*$'

Zamień przecinek na inne ograniczniki.

Tomek
źródło
3
Aby zrobić odwrotnie i znaleźć wszystko oprócz ostatniego pola, wykonaj:grep -o '^.*,'
Ariel
2
Było to szczególnie przydatne, ponieważ revw moim przypadku dodano problem wielobajtowych znaków Unicode.
Brice
3
Próbowałem to zrobić na MinGW, ale moja wersja grep nie obsługuje -o, więc użyłem, sed 's/^.*,//'który zastępuje wszystkie znaki aż do ostatniego przecinka pustym łańcuchem.
TamaMcGlinn
46

Bez awk? ... Ale z awk jest to takie proste:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK jest o wiele potężniejszym narzędziem, które można mieć w kieszeni. -F, jeśli dla separatora pól NF jest liczbą pól (oznacza również indeks ostatniego)

Amir Mehler
źródło
2
Jest to uniwersalne i za każdym razem działa dokładnie zgodnie z oczekiwaniami. W tym scenariuszu użycie cutdo osiągnięcia ostatecznego wyniku PO jest jak użycie łyżki do „pokrojenia” steków (zamierzona gra słów :)). awkjest nóż do steków.
Hickory420
3
Unikaj niepotrzebnego użycia tego, echoponieważ może to spowolnić działanie skryptu przy długich plikach awk -F. '{print $NF}' <<< 'maps.google.com'.
Anil_M
14

Istnieje wiele sposobów. Ty też możesz tego użyć.

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

Oczywiście, puste miejsce dla polecenia tr należy zastąpić potrzebnym ogranicznikiem.

rjni
źródło
Dziękuję Ci! coś, co działa w busybox sh 1.0.0 :)
kevinf 12.04.16
1
To wydaje mi się najprostszą odpowiedzią dla mnie, mniej rur i wyraźniejsze znaczenie
joeButler
1
To nie zadziała dla całego pliku, co prawdopodobnie oznaczało OP.
Amir
7

Jest to jedyne możliwe rozwiązanie, w którym można użyć tylko cięcia:

echo „string” | cut -d '.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | cut -d '.' -f2-

Korzystając z tego rozwiązania, liczba pól może być rzeczywiście nieznana i zmieniać się od czasu do czasu. Ponieważ jednak długość linii nie może przekraczać LINE_MAX znaków lub pól, w tym znaku nowej linii, dowolna liczba pól nigdy nie może być częścią jako rzeczywisty warunek tego rozwiązania.

Tak, bardzo głupie rozwiązanie, ale myślę, że jedyne, które spełnia kryteria.

Przyjaciel
źródło
2
Miły. Po prostu weź ostatnie ”. od „string” i to działa.
Matt.
2
Uwielbiam, kiedy wszyscy mówią, że coś jest niemożliwe, a potem ktoś wpada do pracy z odpowiedzią. Nawet jeśli jest to naprawdę bardzo głupie.
Beejor
Można iterować cut -f2-w pętli, dopóki dane wyjściowe już się nie zmienią.
loa_in_
4

Jeśli ciąg wejściowy nie zawiera ukośników, możesz użyć basenamei podpowłoki:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

To nie używa sedlub awkteż nie używacut albo, więc nie jestem pewien, czy to kwalifikuje się jako odpowiedź na pytanie, jak jego brzmienie.

Nie działa to dobrze, jeśli przetwarzane są ciągi wejściowe, które mogą zawierać ukośniki. Obejściem tej sytuacji byłoby zastąpienie ukośnika przedniego innym znakiem, o którym wiesz, że nie jest częścią prawidłowego ciągu wejściowego. Na przykład |znak pipe ( ) nie jest także dozwolony w nazwach plików, więc działałoby to:

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'
jstine
źródło
2

następujące implementuje sugestię znajomego

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d
użytkownik2166700
źródło
2
Potrzebujesz podwójnych cudzysłowów wokół argumentów echo, aby działało to niezawodnie i solidnie. Zobacz stackoverflow.com/questions/10067266/…
tripleee
0

Jeśli masz plik o nazwie filelist.txt, który jest ścieżką do listy, taką jak: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

możesz to zrobić: rev filelist.txt | cut -d "/" -f1 | obrót silnika

aperson1961
źródło
0

Dodając podejście do tego starego pytania tylko dla zabawy:

$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info

$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do  
    while [[ "$line" =~ "$delim" ]]; do
        line=$(cut -d"$delim" -f 2- <<<"$line")
    done
    echo "$line"
done < input.file

$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info

Oprócz bash stosuje się tylko cięcie. No i echo, tak myślę.

Kaffe Myers
źródło
Eee, dlaczego po prostu całkowicie nie wyciąć i użyć tylko bash ... x] while read -r line; do echo ${line/*;}; done <input.filedaje ten sam wynik.
Kaffe Myers
-1

Uświadomiłem sobie, że jeśli upewnimy się, że istnieje separator końcowy, to zadziała. W moim przypadku mam separatory przecinków i białych znaków. Na końcu dodaję spację;

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b
AnneTheAgile
źródło
I ans="a, b, c"produkuje b, który nie spełnia wymagań „liczba pól jest nieznana lub zmienia się z każdą linią” .
jww