Polecenie drukowania tylko ostatnich 3 znaków ciągu


30

Wiem, że cutpolecenie może wydrukować pierwsze nznaki ciągu, ale jak wybrać ostatnie nznaki?

Jeśli mam ciąg ze zmienną liczbą znaków, w jaki sposób mogę wydrukować tylko trzy ostatnie znaki ciągu. na przykład.

Wymagana „nieograniczona” moc wyjściowa to „ted”
Wymagane wyjście „987654” to „654”
Wymagane wyjście „123456789” to „789”

Odpowiedzi:


52

Dlaczego nikt nie dał oczywistej odpowiedzi?

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

… Lub nieco mniej oczywiste

grep -o '...$'

Trzeba przyznać, że druga ma tę wadę, że znikają linie zawierające mniej niż trzy znaki; ale pytanie nie zdefiniowało wyraźnie zachowania w tej sprawie.


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

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

8
@Kiro lub "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, ale nie sądzę, że naprawdę ciężko jest wprowadzić cięższy język do manipulacji postaciami.
wchargin

11
@WChargin, zapomniałeśjava -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}$'wypisze 3 ostatnie znaki, nawet jeśli linia ma mniej niż 3 znaki. -Ppozwala uniknąć ucieczki z aparatu ortodontycznego.
Raghu Dodda,

43

Prostota - ogon

Nie powinniśmy potrzebować wyrażenia regularnego ani więcej niż jednego procesu, aby policzyć znaki.
Polecenie tail, często używane do wyświetlania ostatnich linii pliku, ma opcję -c( --bytes), która wydaje się być właściwym narzędziem do tego:

$ printf 123456789 | tail -c 3
789

(Gdy jesteś w powłoce, sensowne jest użycie metody takiej jak w odpowiedzi mikeserv, ponieważ oszczędza to rozpoczęcia procesu tail.)

Prawdziwe znaki Unicode?

Teraz pytasz o ostatnie trzy postacie ; Nie to daje ci ta odpowiedź: wyświetla trzy ostatnie bajty !

Tak długo, jak każdy znak ma jeden bajt, tail -cpo prostu działa. Dzięki czemu może być używany, jeśli zestaw znaków ASCII, ISO 8859-1lub jego wariant.

Jeśli masz wejście Unicode, jak we wspólnym UTF-8formacie, wynik jest nieprawidłowy:

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

W tym przykładzie użycie UTF-8greckich znaków alfa, beta i gamma ma dwa bajty:

$ printf 123αβγ | wc -c  
9

Ta opcja -mmoże przynajmniej liczyć prawdziwe znaki Unicode:

printf 123αβγ | wc -m
6

Ok, więc ostatnie 6 bajtów da nam ostatnie 3 znaki:

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

Więc tailnie obsługuje przenoszenia ogólne znaki, i to nawet nie próbować (patrz poniżej): Obsługuje linie zmienne wielkości, ale nie ma znaków o zmiennej wielkości.

Ujmijmy to w następujący sposób: tailjest odpowiedni dla struktury problemu do rozwiązania, ale zły dla rodzaju danych.

Coreutils GNU

Patrząc dalej, okazuje się, że thee coreutils GNU zbiór podstawowych narzędzi podoba sed, ls, taila cutnie jest jeszcze w pełni umiędzynarodowione. Dotyczy to głównie obsługi Unicode.
Na przykład cutbyłby dobrym kandydatem do użycia zamiast ogona tutaj do wspierania postaci; Ma opcje pracy na bajtach lub znakach, -c( --bytes) i -m( --chars);

Tyle, że -m/ --charsod wersji
cut (GNU coreutils) 8.212013
nie jest zaimplementowany!

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


Zobacz także tę odpowiedź na Nie możesz używać `cut -c` (` --characters`) z UTF-8? .


2
W rzeczywistości większość innych odpowiedzi wydaje się dobrze obsługiwać Unicode, o ile obecne ustawienia regionalne określają kodowanie UTF-8. Wydaje się cut, że nie dotyczy to tylko twojego i Glenna Jackmana .
Ilmari Karonen

@IlmariKaronen To prawda, dziękuję za podpowiedź. Edytowałem z pewnymi dodatkowymi szczegółami.
Volker Siegel

1
Zauważ, że POSIX wyraźnie określa, że tailpowinny zajmować się bajtami, a nie znakami. Kiedyś zrobiłem łatkę, aby dodać nową opcję, aby również wybrać postacie, ale wierzę, że nigdy się nie połączyłem: - /
Martin Tournoij

Nie działa w trybie plików, jaktail -c3 -n10 /var/log/syslog
Suncatcher

@Suncatcher Próbowałem i zadziałało. Jaki widzisz problem? Twoje polecenie tail -c3 -n10 /var/log/syslogprosi o ostatnie 10 wierszy i to działa dla mnie. Korzystasz z opcji -c3, a następnie z opcji sprzecznej -n10. Późniejsza opcja ma priorytet.
Volker Siegel

36

Jeśli tekst jest w zmiennej powłoki o nazwie STRING, można to zrobić w sposób bash, zshlub mkshzapłacić:

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

Lub

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

który ma również tę zaletę, że współpracuje z ksh93, skąd pochodzi ta składnia.

Chodzi o to, że :trzeba go oddzielić od -, w przeciwnym razie staje się ${var:-default}operatorem powłoki Bourne'a.

Równoważna składnia w powłokach zshlub yashto:

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

2
Jak nazywa się ten rodzaj składni / operacji, abym mógł wyszukać więcej informacji?
Tulains Córdova

6
Nazywa się to rozszerzaniem podciągów . Jest to rodzaj rozszerzenia parametrów . Ogólna forma to $ {parametr: offset: długość} , ale pole długości jest opcjonalne (i, jak widać, zostało pominięte w odpowiedzi powyżej). DopeGhoti mógł również napisać ${STRING:(-3):3}(określając pole długości ), ${STRING: -3}(ze spacją między :i -), lub ${STRING: -3:3}.
G-Man mówi „Przywróć Monikę”

W tym przypadku określenie długości 3jest nieco dyskusyjne, ponieważ wymaga „trzech znaków od trzeciego od ostatniego znaku włącznie”, co w praktyce jest identyczną operacją jak „Wszystkie postacie od trzeciego od ostatniego , włącznie".
DopeGhoti

13

Używanie awk:

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

11

Jeśli ciąg znajduje się w zmiennej, możesz:

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

To usuwa trzy ostatnie znaki z wartości $varlike:

${var%???}

... a potem zdziera z głowy $varwszystko, ale to, co zostało po prostu pozbawione:

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

Ta metoda ma swoje zalety i wady. Z drugiej strony jest w pełni przenośny dla POSIX i powinien działać w każdej nowoczesnej powłoce. Ponadto, jeśli $varnie zawierają co najmniej trzy znaki nic ale spływu \newline zostanie wydrukowany. Z drugiej strony, jeśli chcesz wydrukować go w takim przypadku, potrzebujesz dodatkowego kroku, takiego jak:

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

W ten sposób $last3jest zawsze pusty, jeśli $varzawiera 3 lub mniej bajtów. I $varjest zawsze zastępowany, $last3jeśli $last3jest pusty lub unset- i wiemy, że nie jest tak, unsetponieważ właśnie go ustawiliśmy.


To dość uporządkowane +1. Poza tym: z jakiegoś powodu nie cytujesz printfciągów formatu?
jasonwryan

Dlaczego nie po prostu użyć ${VARNAME:(-3)}(zakładając bash)?
DopeGhoti

1
Dzięki za wytłumaczenie; ma sens, nawet jeśli wygląda (dla mnie) trochę dziwnie ...
jasonwryan

1
@DopeGhoti - po prostu dlatego, że jest to założenie, którego prawie nigdy nie poczynię. Działa to równie dobrze, bashjak w każdej innej powłoce, która twierdzi, że jest zgodna z POSIX.
mikeserv

3
@odyssey - Problemem jest cshto nie wśród nowoczesnych, zgodnych z POSIX muszli Wspomnę tu, niestety. Modelowana jest specyfikacja powłoki POSIX ksh, która modelowała się po kombinacji obu cshi tradycyjnych powłok w stylu Bourne'a. kshzawiera zarówno cshdoskonałą funkcję kontroli zadań, jak i przekierowanie we / wy starych stylów Bourne'a. Dodał także pewne rzeczy - takie jak koncepcje manipulacji ciągami, które pokazałem powyżej. cshPrzykro mi to mówić, ale to nie będzie działać w żadnym tradycyjnym, o ile mi wiadomo.
mikeserv

7

Możesz to zrobić, ale jest to trochę ... nadmierne:

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

3

Kuloodporne rozwiązanie dla ciągów 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")

Albo użyj:

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

aby zapobiec nieprawidłowej obsłudze danych.

Przykład:

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

Wyprowadza coś takiego:

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

Nie zależy od ustawień regionalnych (tzn. Działa z LC_ALL=C). Bash, sed, grep, awk, revWymagają mniej więcej tak:LC_ALL=en_US.UTF-8

Wspólne rozwiązanie:

  • Otrzymuj bajty
  • Wykryj kodowanie
  • Dekoduj bajty na znaki
  • Wyodrębnij charaktery
  • Zakoduj znak na bajty

Możesz wykryć kodowanie za pomocą uchardet . Zobacz także powiązane projekty .

Możesz dekodować / kodować za pomocą Encode w Perlu, kodeków w Pythonie 2.7

Przykład :

Wyodrębnij ostatnie trzy znaki z ciągu utf-16le i przekonwertuj te znaki na 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"
)

Zobacz także: perlunitut , Python 2 Unicode HOWTO


echojest twoje kuloodporne źródło?
mikeserv

@mikeserv, decode/encodejest moim kuloodpornym źródłem. Oczyściłem moją odpowiedź.
Evgeny Vereshchagin,

Zależy to również od ustawień regionalnych, aby zagwarantować, że działa poprawnie, ponieważ zestaw bajtów może odzwierciedlać różne znaki w różnych zestawach znaków. „Działa”, LC_ALL=Cponieważ jest to bardzo „głupie” ustawienie, ale może się zepsuć, gdy spróbujesz przekazać ciąg UTF-8 do SHIFT-5 lub ciąg SHIFT-5 do KOI8 itp.
Martin Tournoij

@Carpetsmoker, dzięki. Czy możesz wyjaśnić swój komentarz? Przypuszczam, że to perl -CAO -e 'print substr($ARGV[0], -3)'działa dobrze. Aoczekuje się, że elementy @ARGV będą ciągami kodowanymi w UTF-8, OSTDOUT będzie w UTF-8.
Evgeny Vereshchagin,

wygląda na to, że opowiadasz o przydziale doutf8_str
Evgeny Vereshchagin

1

Co powiesz na użycie „expr” lub „rev”?

Odpowiedź podobna do tej udzielonej przez @ G-Man :expr "$yourstring" : '.*\(...\)$' Ma tę samą wadę, co rozwiązanie grep.

Dobrze znaną sztuczką jest łączenie „cięcia” z „obrotem”: echo "$yourstring" | rev | cut -n 1-3 | rev


revRozwiązanie wygląda jak Glenn Jackmana
Jeff Schaller

Masz rację @Jeff_Schaller: Brakowało mi
glenna

0

Uzyskaj rozmiar łańcucha za pomocą:

size=${#STRING}

Następnie zdobądź podłańcuch ostatniego n znaku:

echo ${STRING:size-n:size}

Na przykład:

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

dałby:

789

0

tail -n 1 revisions.log | awk '{print substr (0 USD, 0, długość (0 USD) - (długość (0 USD) -13))}'

Jeśli chcesz wydrukować pierwsze trzynaście znaków od samego początku


-1

printf nie będzie działać, jeśli w łańcuchu znajdują się spacje.

Poniżej kodu dla łańcucha ze spacją

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

nux


Jeśli printfnie działa, robisz coś bardzo złego.
Kusalananda

1
@Kusalananda: Na podstawie polecenia, które pokazuje Saurabh, próbowali printf $str(zamiast printf "$str"lub printf '%s' "$str"). I tak, printf $strjest bardzo źle. ( echo -n $strnie jest dużo lepszy.)
G-Man mówi „Przywróć Monikę”
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.