Un comando per stampare solo gli ultimi 3 caratteri di una stringa


30

So che il cutcomando può stampare i primi ncaratteri di una stringa ma come selezionare gli ultimi ncaratteri?

Se ho una stringa con un numero variabile di caratteri, come posso stampare solo gli ultimi tre caratteri della stringa. per esempio.

L'output "illimitato" è "ted"
L'output "987654" necessario è "654"
L'output "123456789" è "789"

Risposte:


52

Perché nessuno ha dato la risposta ovvia?

sed 's/.*\(...\)/\1/'

... o leggermente meno ovvio

grep -o '...$'

Certo, il secondo presenta l'inconveniente che svaniscono con meno di tre caratteri; ma la domanda non ha definito esplicitamente il comportamento per questo caso.


6
oppuregrep -o '.\{3\}$'
Avinash Raj,

3
oppureecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro,

8
@Kiro o "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, ma non credo sia davvero necessario introdurre un linguaggio più pesante per la manipolazione dei personaggi.
mercoledì

11
@WChargin hai dimenticatojava -server -Xms300M -Xmx3G -XX:+UseParallelGC -cp /path/to/all/the/jars/ -Dinput.interactive=false -Dinput.pipe=true -Dconfig.file=/path/to/config/last-three-letters.cfg -jar ...
HJK

6
grep -o -P '.{0,3}$'stamperà gli ultimi 3 caratteri anche se la riga ha meno di 3 caratteri. -Pevita di dover sfuggire alle parentesi graffe.
Raghu Dodda,

43

Semplificando: coda

Non dovremmo aver bisogno di un'espressione regolare, o più di un processo, solo per contare i caratteri.
Il comando tail, spesso usato per mostrare le ultime righe di un file, ha un'opzione -c( --bytes), che sembra essere lo strumento giusto per questo:

$ printf 123456789 | tail -c 3
789

(Quando sei in una shell, ha senso usare un metodo come nella risposta di mikeserv, perché salva l'avvio del processo per tail.)

Veri personaggi Unicode?

Ora chiedi gli ultimi tre personaggi ; Non è questo che ti dà questa risposta: genera gli ultimi tre byte !

Finché ogni carattere è un byte, tail -cfunziona e basta. Così può essere utilizzato se il set di caratteri è ASCII, ISO 8859-1o una sua variante.

Se si dispone di input Unicode, come nel UTF-8formato comune , il risultato è errato:

$ printf 123αβγ | tail -c 3
�γ

In questo esempio, usando UTF-8, i caratteri greci alpha, beta e gamma sono lunghi due byte:

$ printf 123αβγ | wc -c  
9

L'opzione -mpuò almeno contare i caratteri unicode reali:

printf 123αβγ | wc -m
6

Ok, quindi gli ultimi 6 byte ci daranno gli ultimi 3 caratteri:

$ printf 123αβγ | tail -c 6
αβγ

Quindi, tailnon supporta la gestione di caratteri generali e non ci prova nemmeno (vedi sotto): gestisce linee di dimensioni variabili, ma non caratteri di dimensioni variabili.

Mettiamola così: tailè giusto per la struttura del problema da risolvere, ma sbagliata per il tipo di dati.

Coreutils GNU

Guardando oltre, si scopre che ti coreutils GNU, la collezione di strumenti di base come sed, ls, taile cut, non è ancora pienamente internazionalizzato. Il che riguarda principalmente il supporto di Unicode.
Ad esempio, cutsarebbe un buon candidato da utilizzare al posto della coda qui per il supporto del personaggio; Ha opzioni per lavorare su byte o caratteri, -c( --bytes) e -m( --chars);

Solo che -m/ non --charsè, a partire dalla versione
cut (GNU coreutils) 8.212013,
non implementato!

Da info cut:

`-c CHARACTER-LIST'
`--characters=CHARACTER-LIST'
     Select for printing only the characters in positions listed in CHARACTER-LIST.  
     The same as `-b' for now, but internationalization will change that.


Vedi anche questa risposta a Non puoi usare `cut -c` (` --characters`) con UTF-8? .


2
In realtà, la maggior parte delle altre risposte sembra gestire bene Unicode, purché la locale corrente specifichi la codifica UTF-8. Solo la tua e la cutsoluzione basata su Glenn Jackman non sembrano.
Ilmari Karonen,

@IlmariKaronen Vero, grazie per il suggerimento. Ho modificato, con alcuni dettagli aggiuntivi.
Volker Siegel,

1
Si noti che POSIX specifica esplicitamente che taildeve gestire i byte e non i caratteri. Una volta ho fatto una patch per aggiungere una nuova opzione per selezionare anche i personaggi, ma credo che non sia mai stato unito: - /
Martin Tournoij

Non funziona in modalità file, ad esempiotail -c3 -n10 /var/log/syslog
Suncatcher

@Suncatcher ho provato e ha funzionato. Qual è il problema che vedi? Il tuo comando tail -c3 -n10 /var/log/syslogrichiede le ultime 10 righe e questo funziona per me. Si utilizza l'opzione -c3e successivamente l'opzione in conflitto -n10. L'opzione successiva ha la priorità.
Volker Siegel,

36

Se il tuo testo è in una variabile shell chiamata STRING, puoi farlo in a bash, zsho mkshshell:

printf '%s\n' "${STRING:(-3)}"

O

printf '%s\n' "${STRING: -3}"

che ha anche il vantaggio di lavorare con ksh93 da dove proviene quella sintassi.

Il punto è che :deve essere separato da -, altrimenti diventa l' ${var:-default}operatore della shell Bourne.

La sintassi equivalente nelle shell zsho yashè:

printf '%s\n' "${STRING[-3,-1]}"

2
Come si chiama quel tipo di sintassi / operazione per poter cercare ulteriori informazioni?
Tulains Córdova,

6
Si chiama Substring Expansion . È una specie di espansione dei parametri . Il modulo generale è $ {parametro: offset: lunghezza} , ma il campo lunghezza è facoltativo (e, come puoi vedere, è stato omesso nella risposta sopra). DopeGhoti potrebbe anche aver scritto ${STRING:(-3):3}(specificando il campo della lunghezza ), ${STRING: -3}(con uno spazio tra la :e la -), oppure ${STRING: -3:3}.
G-Man dice "Reinstate Monica" il

In questo caso, specificare la lunghezza di 3è un po 'controverso in quanto richiede "i tre caratteri del terzo dall'ultimo carattere, compreso" che risulta essere un'operazione identica in termini pratici a "Tutti i caratteri in poi dal terzo dall'ultimo , inclusivo ".
DopeGhoti,

13

Utilizzando awk:

awk '{ print substr( $0, length($0) - 2, length($0) ) }' file
ted
654
789

11

Se la stringa è in una variabile puoi fare:

printf %s\\n "${var#"${var%???}"}"

Ciò toglie gli ultimi tre caratteri dal valore di $varlike:

${var%???}

... e poi si spoglia dalla testa di $vartutto, ma di ciò che è stato appena spogliato:

${var#"${var%???}"}

Questo metodo ha i suoi lati positivi e negativi. Il lato positivo è completamente portatile POSIX e dovrebbe funzionare in qualsiasi shell moderno. Inoltre, se $varnon contiene almeno tre caratteri nulla , ma il finale \newline viene stampato. Poi di nuovo, se vuoi che sia stampato in quel caso, hai bisogno di un passaggio aggiuntivo come:

last3=${var#"${var%???}"}
printf %s\\n "${last3:-$var}"

In questo modo $last3è sempre e solo vuoto se $varcontiene 3 o meno byte. Ed $varè sempre e solo sostituito $last3se $last3è vuoto o unset- e sappiamo che non è unsetperché lo abbiamo appena impostato.


Questo è abbastanza ordinato +1. A parte: qualche ragione per non citare le printfstringhe di formato?
Jasonwryan,

Perché non usare ${VARNAME:(-3)}(presumendo bash)?
DopeGhoti,

1
Grazie per il chiarimento; ha senso, anche se sembra (per me) un po 'strano ...
Jasonwryan,

1
@DopeGhoti - semplicemente perché è un presupposto che non faccio quasi mai. Funziona così bashcome in qualsiasi altra shell che rivendichi la compatibilità con POSIX.
mikeserv,

3
@odyssey - Il problema noncsh è tra le moderne shell compatibili con POSIX che cito qui, sfortunatamente. Le specifiche della shell POSIX sono modellate dopo , che si è modellato su una combinazione di entrambe e delle tradizionali shell in stile Bourne. incorporava l'eccellente funzionalità di controllo del lavoro e il reindirizzamento I / O dei vecchi stili Bourne. Ha anche aggiunto alcune cose - come i concetti di manipolazione delle stringhe che ho dimostrato sopra. Questo probabilmente non funzionerà in nessun modo tradizionale per quanto ne so, mi dispiace dirlo. kshcshkshcshcsh
Mikeserv,

7

Puoi farlo, ma questo è un po '... eccessivo:

for s in unlimited 987654 123456789; do
    rev <<< $s | cut -c 1-3 | rev
done 
ted
654
789

3

La soluzione antiproiettile per stringhe utf-8:

utf8_str=$'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' # привет

last_three_chars=$(perl -CAO -e 'print substr($ARGV[0], -3)' "$utf8_str")

Oppure usa:

last_three_chars=$(perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' "$utf8_str")

per impedire la gestione non corretta dei dati.

Esempio:

perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' $'\xd0\xd2\xc9\xd7\xc5\xd4' # koi8-r привет

Emette qualcosa del genere:

utf8 "\xD0" does not map to Unicode at /usr/lib/x86_64-linux-gnu/perl/5.20/Encode.pm line 175.

Non dipende dalle impostazioni locali (cioè funziona con LC_ALL=C). Bash, sed, grep, awk, revRichiedono qualcosa di simile a questo:LC_ALL=en_US.UTF-8

Soluzione comune:

  • Ricevi byte
  • Rileva la codifica
  • Decodifica i byte in caratteri
  • Estrai i caratteri
  • Codifica il carattere in byte

Puoi rilevare la codifica con uchardet . Vedi anche progetti correlati .

Puoi decodificare / codificare con Encode in Perl, codec in Python 2.7

Esempio :

Estrai gli ultimi tre caratteri dalla stringa utf-16le e converti questi caratteri in utf-8

utf16_le_str=$'\xff\xfe\x3f\x04\x40\x04\x38\x04\x32\x04\x35\x04\x42\x04' # привет

chardet <<<"$utf16_le_str"  # outputs <stdin>: UTF-16LE with confidence 1.0

last_three_utf8_chars=$(perl -MEncode -e '
    my $chars = decode("utf-16le", $ARGV[0]);
    my $last_three_chars = substr($chars, -3);
    my $bytes = encode("utf-8", $last_three_chars);
    print $bytes;
  ' "$utf16_le_str"
)

Vedi anche: perlunitut , Python 2 Unicode HOWTO


echoè la tua fonte antiproiettile?
Mikeserv,

@mikeserv, decode/encodeè la mia fonte antiproiettile. Pulito la mia risposta.
Evgeny Vereshchagin,

Ciò dipende anche dalle impostazioni locali per garantire che funzioni correttamente, poiché un set di byte può riflettere caratteri diversi in diversi set di caratteri. "Funziona" LC_ALL=Cperché è un'impostazione molto "stupida", ma potrebbe interrompersi quando si tenta di passare una stringa UTF-8 a SHIFT-5 o una stringa SHIFT-5 a KOI8, ecc.
Martin Tournoij

@Carpetsmoker, grazie. Potresti spiegare il tuo commento? Suppongo che perl -CAO -e 'print substr($ARGV[0], -3)'funzioni bene. Asi prevede che gli elementi @ARGV siano stringhe codificate in UTF-8, OSTDOUT sarà in UTF-8.
Evgeny Vereshchagin,

sembra che tu abbia parlato dell'incarico diutf8_str
Evgeny Vereshchagin il

1

Che dire dell'utilizzo di "expr" o "rev"?

Una risposta simile a quella fornita da @ G-Man : expr "$yourstring" : '.*\(...\)$' ha lo stesso inconveniente della soluzione grep.

Un trucco ben noto è quello di combinare "tagliare" con "rev": echo "$yourstring" | rev | cut -n 1-3 | rev


La revsoluzione assomiglia molto a quella di Glenn Jackman
Jeff Schaller

Hai ragione @Jeff_Schaller: mi mancava quella di
Glenn

0

Ottieni la dimensione della stringa con:

size=${#STRING}

Quindi ottieni la sottostringa dell'ultimo n carattere:

echo ${STRING:size-n:size}

Per esempio:

STRING=123456789
n=3
size=${#STRING}
echo ${STRING:size-n:size}

darebbe:

789

0

tail -n 1 revisions.log | awk '{stampa substr ($ 0, 0, lunghezza ($ 0) - (lunghezza ($ 0) -13))}'

Se vuoi stampare i primi tredici caratteri dall'inizio


-1

printf non funzionerà se la stringa contiene spazi.

Sotto il codice per stringa con spazio

str="Welcome to Linux"
echo -n $str | tail -c 3

nux


Ehm, se printfnon funziona, allora stai facendo qualcosa di molto sbagliato.
Kusalananda

1
@Kusalananda: in base al comando mostrato da Saurabh, hanno provato printf $str(piuttosto che printf "$str"o printf '%s' "$str"). E sì, printf $strè molto sbagliato. ( echo -n $strnon è molto meglio.)
G-Man dice 'Reinstate Monica'
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.