Come trovare l'ultimo campo usando 'cut'


310

Senza usare sedo awk, solo cut , come posso ottenere l'ultimo campo quando il numero di campi è sconosciuto o cambia con ogni riga?


8
Sei innamorato del cutcomando :)? perché non altri comandi Linux?
Jayesh Bhoi,

7
Senza sedo awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
Giordania,


4
@MestreLion Molte volte le persone leggono una domanda per trovare una soluzione a una variazione di un problema. Questo inizia con la premessa falsa che cutsupporta qualcosa che non lo fa. Ma ho pensato che fosse utile, in quanto costringe il lettore a considerare il codice che è più facile da seguire. Volevo un rapido, modo semplice per l'utilizzo cutsenza la necessità di utilizzare più sintassi per awk, grep, sed, ecc La revcosa che ha fatto il trucco; molto elegante e qualcosa che non ho mai considerato (anche se goffo per altre situazioni). Mi è piaciuto anche leggere gli altri approcci dalle altre risposte.
Beejor,

3
È venuto qui un vero problema di vita: voglio trovare tutte le diverse estensioni di file in un albero dei sorgenti, con cui aggiornare un file .gitattributes. Così find | cut -d. -f<last>è l'inclinazione naturale
studog

Risposte:


680

Potresti provare qualcosa del genere:

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

Spiegazione

  • rev inverte "maps.google.com" per essere moc.elgoog.spam
  • cut usa il punto (cioè '.') come delimitatore e sceglie il primo campo, che è moc
  • infine, lo invertiamo di nuovo per ottenere com

6
Non usa solo cutma è senza sedo awk. Quindi cosa ne pensa OP?
Jayesh Bhoi,

7
@tom OP ha posto più domande di questo nelle ultime ore. Sulla base delle nostre interazioni con l'OP sappiamo che awk / sed / etc. non sono ammessi a fare i compiti, ma non è stato fatto un riferimento al rev. Quindi valeva la pena
provare

4
@zfus vedo. Potrebbe voler attaccarne un altro revdopo.
Tom,

17
doppio revottimo ideale!
Ford Guo,

6
Fantastico, semplice, perfetto, grazie anche per la spiegazione - non abbastanza persone che spiegano ogni passaggio in lunghe catene di comandi inoltrati
Pete

128

Utilizzare un'espansione dei parametri. Questo è molto più efficiente di qualsiasi tipo di comando esterno cut(o grep) incluso.

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

Vedi BashFAQ # 100 per un'introduzione alla manipolazione di stringhe native in bash.


3
@ErwinWessels: Perché bash è molto lento. Utilizzare bash per eseguire pipeline, non per elaborare dati in blocco. Voglio dire, è grandioso se hai già una riga di testo in una variabile shell o se vuoi fare while IFS= read -ra array_var; do :;done <(cmd)qualche riga. Ma per un file di grandi dimensioni, rev | cut | rev è probabilmente più veloce! (E ovviamente Awk sarà più veloce di così.)
Peter Cordes,

2
@PeterCordes, awk sarà più veloce per un file di grandi dimensioni, certo, ma ci vuole un bel po 'di input per superare i costi di avvio a fattore costante. (Esistono anche shell - come ksh93 - con prestazioni più vicine a awk, in cui la sintassi fornita in questa risposta rimane valida; bash è eccezionalmente lento, ma non è nemmeno vicino all'unica opzione disponibile).
Charles Duffy,

1
Grazie @PeterCordes; come al solito immagino che ogni strumento abbia i suoi casi d'uso.
Erwin Wessels,

1
Questo è di gran lunga il modo più veloce e conciso per tagliare una singola variabile all'interno di uno bashscript (supponendo che tu stia già utilizzando uno bashscript). Non c'è bisogno di chiamare nulla di esterno.
Ken Sharp,

1
@Balmipour, ... tuttavia, rev è specifico per qualsiasi sistema operativo in uso che lo fornisce - non è standardizzato su tutti i sistemi UNIX. Vedi l' elenco dei capitoli per la sezione POSIX su comandi e utilità - non è presente. E in realtà non${var##prefix_pattern} è specifico per Bash; è nello standard POSIX sh , vedere la fine della sezione 2.6.2 (collegata), quindi a differenza di , è sempre disponibile su qualsiasi shell conforme. rev
Charles Duffy,

89

Non è possibile usare solo cut. Ecco un modo usando grep:

grep -o '[^,]*$'

Sostituisci la virgola per altri delimitatori.


3
Per fare il contrario, e trovare tutto tranne l'ultimo campo:grep -o '^.*,'
Ariel

2
Questo è stato particolarmente utile, perché revnel mio caso aggiungo un problema con caratteri unicode multibyte.
Brice,

3
Stavo provando a farlo su MinGW ma la mia versione grep non supporta -o, quindi ho usato sed 's/^.*,//'che sostituisce tutti i caratteri fino all'ultima virgola inclusa e con una stringa vuota.
TamaMcGlinn,

46

Senza awk? ... Ma è così semplice con awk:

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

AWK è uno strumento molto più potente da avere in tasca. -F se per il separatore di campo NF è il numero di campi (indica anche l'indice dell'ultimo)


2
Questo è universale e funziona esattamente come previsto ogni volta. In questo scenario, usare cutper ottenere l'output finale dell'OP è come usare un cucchiaio per "tagliare" la bistecca (gioco di parole :)). awkè il coltello da bistecca.
Hickory420,

3
Evitare l'uso non necessario di echoquesto può rallentare lo script per l'utilizzo di file lunghi awk -F. '{print $NF}' <<< 'maps.google.com'.
Anil_M,

14

Ci sono molti modi. Puoi usare anche questo.

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

Ovviamente, l'input di spazio vuoto per il comando tr deve essere sostituito con il delimitatore necessario.


Grazie! qualcosa che funziona in busybox sh 1.0.0 :)
kevinf

1
Mi sembra la risposta più semplice, meno pipe e significato più chiaro
joeButler

1
Ciò non funzionerà per un intero file, il che probabilmente significava l'OP.
Amir,

7

Questa è l'unica soluzione possibile per usare nient'altro che tagliare:

echo "stringa" | cut -d '.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | cut -d '.' -f2-

Utilizzando questa soluzione, il numero di campi può effettivamente essere sconosciuto e variare di volta in volta. Tuttavia, poiché la lunghezza della riga non deve superare i caratteri LINE_MAX o i campi, incluso il carattere di nuova riga, un numero arbitrario di campi non può mai far parte come condizione reale di questa soluzione.

Sì, una soluzione molto sciocca ma l'unica che soddisfa i criteri penso.


2
Bello. Prendi l'ultimo "." di "stringa" e questo funziona.
Matt,

2
Adoro quando tutti dicono che qualcosa è impossibile e poi qualcuno entra con una risposta funzionante. Anche se è davvero molto sciocco.
Beejor,

Si potrebbe iterare cut -f2-in un ciclo fino a quando l'output non cambia più.
loa_in_

4

Se la stringa di input non contiene barre rovesciate, è possibile utilizzare basenamee una subshell:

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

Questo non usa sedo awkma non usa nemmenocut neanche, quindi non sono del tutto sicuro se si qualifica come una risposta alla domanda come è formulato.

Questo non funziona bene se si elaborano stringhe di input che possono contenere barre. Una soluzione alternativa per quella situazione sarebbe quella di sostituire la barra con qualche altro carattere che sai non fa parte di una stringa di input valida. Ad esempio, anche il carattere pipe ( |) non è consentito nei nomi di file, quindi funzionerebbe:

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


0

Se si dispone di un file denominato filelist.txt che è un elenco di percorsi come il seguente: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

allora puoi farlo: rev filelist.txt | cut -d "/" -f1 | rev


0

Aggiungendo un approccio a questa vecchia domanda solo per divertimento:

$ 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

Oltre a bash, viene utilizzato solo il taglio. Bene, ed eco, immagino.


Meh, perché non rimuovere completamente il taglio e usare solo bash ... x] while read -r line; do echo ${line/*;}; done <input.fileproduce lo stesso risultato.
Kaffe Myers,

-1

Mi sono reso conto che se solo assicurassimo che esista un delimitatore finale, funziona. Quindi nel mio caso ho delimitatori di virgola e spazi bianchi. Aggiungo uno spazio alla fine;

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b

E ans="a, b, c"produce b, che non soddisfa i requisiti di "il numero di campi è sconosciuto o cambia con ogni riga" .
1919
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.