Ein Befehl zum Drucken der letzten drei Zeichen einer Zeichenfolge

30

Ich weiß, dass der cutBefehl die ersten nZeichen einer Zeichenfolge drucken kann, aber wie wählt man die letzten nZeichen aus?

Wenn ich eine Zeichenfolge mit einer variablen Anzahl von Zeichen habe, wie kann ich nur die letzten drei Zeichen der Zeichenfolge drucken. z.B.

"Unbegrenzte" Ausgabe erforderlich ist "Ted"
"987654" Ausgang benötigt wird "654"
"123456789" benötigt die Ausgabe "789"
Odyssee
quelle

Antworten:

52

Warum hat niemand die offensichtliche Antwort gegeben?

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

… Oder das etwas weniger offensichtliche

grep -o '...$'

Zugegeben, die zweite hat den Nachteil, dass Zeilen mit weniger als drei Zeichen verschwinden; Aber die Frage hat das Verhalten für diesen Fall nicht explizit definiert.

G-Man sagt, "Monica wiedereinsetzen"
quelle
6
odergrep -o '.\{3\}$'
Avinash Raj
3
oderecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro
8
@Kiro oder "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, aber ich denke nicht, dass es wirklich notwendig ist, eine schwerere Sprache für die Manipulation von Charakteren einzuführen.
Wchargin
11
@WChargin Sie vergessenjava -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}$'Gibt die letzten 3 Zeichen aus, auch wenn die Zeile weniger als 3 Zeichen enthält. -Pvermeidet es, den Zahnspangen zu entkommen.
Raghu Dodda
43

Halte es einfach - Schwanz

Wir sollten keinen regulären Ausdruck oder mehr als einen Prozess benötigen, nur um Zeichen zu zählen.
Der Befehl tail, der häufig zum Anzeigen der letzten Zeilen einer Datei verwendet wird, verfügt über eine option -c( --bytes), die genau das richtige Werkzeug dafür zu sein scheint:

$ printf 123456789 | tail -c 3
789

(Wenn Sie sich in einer Shell befinden, ist es sinnvoll, eine Methode wie in der Antwort von mikeserv zu verwenden, da dies das Starten des Prozesses für spart tail.)

Echte Unicode-Zeichen?

Nun fragen Sie nach den letzten drei Zeichen . Das gibt Ihnen diese Antwort nicht: Sie gibt die letzten drei Bytes aus !

Solange jedes Zeichen ein Byte ist, tail -cfunktioniert es einfach. So ist es verwendet werden kann , wenn der Zeichensatz ist ASCII, ISO 8859-1oder eine Variante.

Wenn Sie eine Unicode-Eingabe haben, wie im allgemeinen UTF-8Format, ist das Ergebnis falsch:

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

In diesem Beispiel sind UTF-8die griechischen Zeichen alpha, beta und gamma mit zwei Bytes lang:

$ printf 123αβγ | wc -c  
9

Die Option -mkann mindestens die echten Unicode-Zeichen zählen:

printf 123αβγ | wc -m
6

Ok, die letzten 6 Bytes geben uns die letzten 3 Zeichen:

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

So tailbietet keine Unterstützung für allgemeine Zeichen Handhabung und versucht es nicht einmal (siehe unten): Es behandelt variabler Größe Linien, aber keine variable Größe Zeichen.

Sagen wir es so: Es tailist genau richtig für die Struktur des Problems, aber falsch für die Art der Daten.

GNU coreutils

Suchen Sie weiter, es stellt sich heraus, der dich GNU coreutils, die Sammlung von grundlegenden Werkzeuge wie sed, ls, tailund cutist noch nicht vollständig internationalisiert. Dabei geht es hauptsächlich um die Unterstützung von Unicode.
Wäre zum Beispiel cutein guter Kandidat, um hier zur Charakterunterstützung anstelle von Tail zu verwenden. Es gibt Optionen zum Bearbeiten von Bytes oder Zeichen -c( --bytes) und -m( --chars);

Nur das -m/ --charsist ab Version
cut (GNU coreutils) 8.212013
nicht implementiert!

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


Siehe auch diese Antwort zu Kann nicht `cut -c` (` --characters`) mit UTF-8 verwendet werden? .

Volker Siegel
quelle
2
Tatsächlich scheinen die meisten anderen Antworten mit Unicode gut zurechtzukommen, solange das aktuelle Gebietsschema die UTF-8-Codierung angibt. Nur Ihre und Glenn Jackmans cutLösung scheint es nicht zu sein.
Ilmari Karonen
@IlmariKaronen Stimmt, danke für den Hinweis. Ich habe mit einigen zusätzlichen Details bearbeitet.
Volker Siegel
1
Beachten Sie, dass POSIX explizit angibt, dass tailmit Bytes und nicht mit Zeichen umgegangen werden soll. Ich habe einmal einen Patch gemacht, um eine neue Option hinzuzufügen, mit der auch Charaktere ausgewählt werden können, aber ich glaube, das wurde nie zusammengeführt: - /
Martin Tournoij
Funktioniert nicht im tail -c3 -n10 /var/log/syslog
Dateimodus
@Suncatcher Ich habe es versucht, und es hat funktioniert. Was ist das Problem, das Sie sehen? Ihr Befehl tail -c3 -n10 /var/log/syslogfragt nach den letzten 10 Zeilen, und das funktioniert bei mir. Sie verwenden die Option -c3und anschließend die widersprüchliche Option -n10. Die spätere Option hat Priorität.
Volker Siegel
36

Wenn Sie Ihr Text in einem Shell - Variable genannt ist STRING, können Sie dies in einem tun bash, zshoder mkshShell:

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

Oder

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

Das hat auch den Vorteil, mit ksh93 zu arbeiten, von dem diese Syntax stammt.

Der Punkt ist, dass das :vom getrennt werden muss -, sonst wird es zum ${var:-default}Operator der Bourne-Shell.

Die äquivalente Syntax in den zshoder yashShells lautet:

printf '%s\n' "${STRING[-3,-1]}"
DopeGhoti
quelle
2
Wie heißt diese Art von Syntax / Operation, damit ich nach weiteren Informationen suchen kann?
Tulains Córdova
6
Es heißt Substring Expansion . Es ist eine Art Parametererweiterung . Die allgemeine Form ist $ {parameter: offset: length} , aber das Längenfeld ist optional (und, wie Sie sehen können, wurde es in der obigen Antwort weggelassen). DopeGhoti könnte auch geschrieben haben ${STRING:(-3):3}(unter Angabe des Längenfeldes ) ${STRING: -3}(mit einem Leerzeichen zwischen :und -) oder ${STRING: -3:3}.
G-Man sagt, dass Monica am
In diesem Fall ist die Angabe der Länge von 3etwas umstritten, da hier nach "den drei Zeichen vom dritten bis einschließlich zum letzten Zeichen" gefragt wird, was in der Praxis mit "Alle Zeichen ab dem dritten bis zum letzten Zeichen" identisch ist ".
DopeGhoti
13

Verwenden von awk:

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

Wenn sich die Zeichenfolge in einer Variablen befindet, können Sie Folgendes tun:

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

Das entfernt die letzten drei Zeichen vom Wert von $varlike:

${var%???}

... und dann von allem den Kopf $varabziehen, aber wie wurde nur abgezogen:

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

Diese Methode hat ihre Vor- und Nachteile. Auf der positiven Seite ist es vollständig POSIX-portabel und sollte in jeder modernen Shell funktionieren. Wenn $varnicht mindestens drei Zeichen enthalten sind , wird nur die \nnachfolgende ewline gedruckt. Wenn Sie es in diesem Fall drucken möchten , benötigen Sie einen zusätzlichen Schritt wie:

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

Auf diese Weise $last3ist immer nur leer, wenn $var3 oder weniger Bytes enthalten sind. Und $varwird immer nur ersetzt, $last3wenn $last3leer ist oder unset- und wir wissen, es liegt nicht unsetdaran, dass wir es nur gesetzt haben.

mikeserv
quelle
Das ist ziemlich ordentlich +1. Abgesehen davon: Aus irgendeinem Grund zitieren Sie Ihre printfFormatzeichenfolgen nicht?
Jasonwryan
Warum nicht einfach benutzen ${VARNAME:(-3)}(voraussetzen bash)?
DopeGhoti
1
Danke fürs klarstellen; Sinn macht, auch wenn es (für mich) ein wenig seltsam aussieht ...
jasonwryan
1
@ DopeGhoti - einfach weil das eine Annahme ist, die ich so gut wie nie mache. Dies funktioniert genauso bashwie in jeder anderen Shell, die POSIX-Kompatibilität beansprucht.
mikeserv
3
@odyssey - Das Problem ist , cshist nicht unter den modernen, POSIX-kompatible Shells ich hier erwähnen, leider. Die POSIX-Shell-Spezifikation wurde nach kshdem Vorbild erstellt, das sich aus einer Kombination der beiden cshund der traditionellen Bourne-Shells zusammensetzt. kshintegrierte sowohl cshdie hervorragende Jobsteuerungsfunktionalität als auch die I / O-Umleitung der alten Bourne-Stile. Es wurden auch einige Dinge hinzugefügt - wie die oben gezeigten Konzepte zur String-Manipulation. cshSoweit ich weiß, wird dies wahrscheinlich in keinem traditionellen System funktionieren. Es tut mir leid, das zu sagen.
mikeserv
7

Sie können dies tun, aber das ist ein wenig ... übertrieben:

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

Die kugelsichere Lösung für utf-8 Saiten:

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

Oder benutze:

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

um den fehlerhaften Umgang mit Daten zu verhindern.

Beispiel:

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

Gibt so etwas aus:

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

Hängt nicht von den Gebietsschemaeinstellungen ab (dh funktioniert mit LC_ALL=C). Bash, sed, grep, awk, revBenötigen etwas wie folgt aus :LC_ALL=en_US.UTF-8

Gemeinsame Lösung:

  • Bytes empfangen
  • Codierung erkennen
  • Dekodiere Bytes in Zeichen
  • Zeichen extrahieren
  • Zeichen in Bytes verschlüsseln

Sie können die Codierung mit uchardet erkennen . Siehe auch verwandte Projekte .

Sie können mit Encode in Perl, Codecs in Python 2.7 decodieren / codieren

Beispiel :

Extrahieren Sie die letzten drei Zeichen aus der Zeichenfolge utf-16le und konvertieren Sie diese Zeichen 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"
)

Siehe auch: perlunitut , Python 2 Unicode HOWTO

Evgeny Vereshchagin
quelle
echoist Ihre kugelsichere Quelle?
mikeserv
@mikeserv, decode/encodeist meine kugelsichere Quelle. Meine Antwort aufgeräumt.
Evgeny Vereshchagin
Dies hängt auch von den Gebietsschemaeinstellungen ab, um sicherzustellen, dass es ordnungsgemäß funktioniert, da ein Satz von Bytes möglicherweise unterschiedliche Zeichen in verschiedenen Zeichensätzen widerspiegelt. Es "funktioniert", LC_ALL=Cweil das eine sehr "blöde" Einstellung ist, aber es kann brechen, wenn Sie versuchen, eine UTF-8-Zeichenfolge an SHIFT-5 oder eine SHIFT-5-Zeichenfolge an KOI8 usw. zu übergeben.
Martin Tournoij
@Carpetsmoker, danke. Können Sie uns Ihren Kommentar erläutern? Ich nehme an, das perl -CAO -e 'print substr($ARGV[0], -3)'funktioniert gut. AEs wird erwartet, dass die @ ARGV-Elemente Zeichenfolgen sind, die in UTF-8 codiert sind, OSTDOUT in UTF-8.
Evgeny Vereshchagin
Sieht aus wie Sie über die Zuordnung zu erzählenutf8_str
Evgeny Vereshchagin
1

Was ist mit "expr" oder "rev"?

Eine Antwort ähnlich der von @ G-Man : expr "$yourstring" : '.*\(...\)$' Sie hat den gleichen Nachteil wie die grep-Lösung.

Ein bekannter Trick ist, "cut" mit "rev" zu kombinieren: echo "$yourstring" | rev | cut -n 1-3 | rev

gildux
quelle
Die revLösung ähnelt
Jeff Schaller
Du hast recht @Jeff_Schaller: Ich habe Glenns verpasst :-(
gildux 13.11.15
0

Größe der Zeichenkette ermitteln mit:

size=${#STRING}

Dann erhalten Sie den Teilstring des letzten n-Zeichens:

echo ${STRING:size-n:size}

Beispielsweise:

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

Würde geben:

789
Esref
quelle
0

tail -n 1 revisions.log | awk '{print substr ($ 0, 0, Länge ($ 0) - (Länge ($ 0) -13)}'

Wenn Sie die ersten dreizehn Zeichen von Anfang an drucken möchten

Ankit Vishwakarma
quelle
-1

printf funktioniert nicht, wenn der String Leerzeichen enthält.

Unten Code für Zeichenfolge mit Leerzeichen

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

nux

Saurabh
quelle
Ähm, wenn printfes nicht funktioniert, dann machen Sie etwas sehr Falsches.
Kusalananda
1
@Kusalananda: Basierend auf dem Befehl, den Saurabh zeigt, haben sie es versucht printf $str(anstatt printf "$str"oder printf '%s' "$str"). Und ja, printf $strist sehr falsch. ( echo -n $strist nicht viel besser.)
G-Man sagt "Reinstate Monica"