Команда для печати только последних 3 символов строки

30

Я знаю, что cutкоманда может печатать первые nсимволы строки, но как выбрать последние nсимволы?

Если у меня есть строка с переменным количеством символов, как я могу напечатать только последние три символа строки. например.

"неограниченный" вывод необходим "тэд"
Требуется вывод «987654» «654»
Требуется вывод «123456789» «789»
Одиссея
источник

Ответы:

52

Почему никто не дал очевидный ответ?

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

... или чуть менее очевидный

grep -o '...$'

Следует признать, что второй недостаток заключается в том, что строки, содержащие менее трех символов, исчезают; но вопрос не определил явно поведение для этого случая.

G-Man говорит: «Восстанови Монику»
источник
6
илиgrep -o '.\{3\}$'
Авинаш Радж
3
илиecho "unlimited" | python -c "print raw_input()[-3:]"
Киро
8
@ Киро или "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, но я не думаю, что действительно нужно вводить более тяжелый язык для манипулирования персонажами.
wchargin
11
@WChargin вы забыли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}$'напечатает последние 3 символа, даже если строка содержит менее 3 символов. -Pизбегает необходимости избегать скобок.
Рагху Додда
43

Сохраняя это простым - хвост

Нам не нужно регулярное выражение или более одного процесса только для подсчета символов.
Команда tail, часто используемая для отображения последних строк файла, имеет параметр -c( --bytes), который, кажется, является подходящим инструментом для этого:

$ printf 123456789 | tail -c 3
789

(Когда вы находитесь в оболочке, имеет смысл использовать метод, как в ответе mikeserv, потому что он экономит запуск процесса для tail.)

Реальные символы Юникода?

Теперь вы просите последние три символа ; Это не то, что дает этот ответ: он выводит последние три байта !

Пока каждый символ - один байт, tail -cпросто работает. Таким образом, он может быть использован, если набор символов ASCII, ISO 8859-1или вариант.

Если у вас есть ввод Unicode, как в обычном UTF-8формате, результат неправильный:

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

В этом примере, используя UTF-8греческие символы альфа, бета и гамма длиной два байта:

$ printf 123αβγ | wc -c  
9

Опция -mможет по крайней мере подсчитывать реальные символы Юникода:

printf 123αβγ | wc -m
6

Итак, последние 6 байтов дадут нам последние 3 символа:

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

Таким образом, tailон не поддерживает обработку общих символов и даже не пытается (см. Ниже): он обрабатывает строки переменного размера, но не символы переменного размера.

Скажем так: tailэто правильно для структуры проблемы, которую нужно решить, но неправильно для вида данных.

GNU coreutils

Глядя дальше, то получается, что Thee Coreutils GNU, совокупность основных инструментов , таких как sed, ls, tailи cut, еще не в полной мере интернационализации. Что в основном о поддержке Unicode.
Например, cutбудет хорошим кандидатом для использования вместо хвоста здесь для поддержки символов; У него есть опции для работы с байтами или символами, -c( --bytes) и -m( --chars);

Только что -m/ --charsесть в версии
cut (GNU coreutils) 8.212013 года ,
не реализован!

От 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.


См. Также этот ответ на Не можете использовать `cut -c` (` --characters`) с UTF-8? ,

Volker Siegel
источник
2
На самом деле, большинство других ответов, похоже, отлично справляются с Юникодом, если текущая локаль определяет кодировку UTF-8. Только ваше и решение Гленна Джекмана, cutпохоже, не подходят .
Илмари Каронен
@IlmariKaronen Правда, спасибо за подсказку. Я отредактировал, с некоторыми дополнительными деталями.
Фолькер Сигел
1
Обратите внимание, что POSIX явно указывает, что tailдолжен иметь дело с байтами, а не символами. Однажды я сделал патч, чтобы добавить новую опцию, чтобы также выбирать персонажей, но я считаю, что никогда не сливался: - /
Мартин Турной
Не работает в файловом режиме, какtail -c3 -n10 /var/log/syslog
Suncatcher
@Suncatcher Я попробовал, и это сработало. Какую проблему вы видите? Ваша команда tail -c3 -n10 /var/log/syslogзапрашивает последние 10 строк, и это работает для меня. Вы используете вариант -c3, а после этого конфликтующий вариант -n10. Последний вариант имеет приоритет.
Фолькер Сигел
36

Если текст в переменной оболочки называется STRING, вы можете сделать это в bash, zshили mkshоболочки:

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

Или

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

который также имеет преимущество для работы с ksh93, откуда исходит этот синтаксис.

Дело в том, что :он должен быть отделен от -, в противном случае он становится ${var:-default}оператором оболочки Борна.

Эквивалентный синтаксис в оболочке zshили yash:

printf '%s\n' "${STRING[-3,-1]}"
DopeGhoti
источник
2
Как называется этот синтаксис / операция, чтобы я мог искать больше информации?
Тулаинс Кордова
6
Это называется расширение подстроки . Это своего рода расширение параметров . Общая форма: $ {параметр: смещение: длина} , но поле длины является необязательным (и, как вы можете видеть, оно было опущено в ответе выше). DopeGhoti также мог бы написать ${STRING:(-3):3}(указав поле длины ), ${STRING: -3}(с пробелом между :и -), или ${STRING: -3:3}.
G-Man говорит: «Восстановите Монику»
В этом случае указание длины 3является несколько спорным, поскольку запрашивается «три символа от третьего до последнего символа включительно», что на практике является операцией, идентичной операции «Все символы начиная с третьего от последнего» включительно ".
DopeGhoti
13

Использование awk:

awk '{ print substr( $0, length($0) - 2, length($0) ) }' file
ted
654
789
jasonwryan
источник
11

Если строка находится в переменной, вы можете сделать:

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

Это лишает последние три символа значения $varlike:

${var%???}

... а затем снимает с головы $varвсе, кроме того, что было просто раздето:

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

Этот метод имеет свои плюсы и минусы. С другой стороны, он полностью переносим POSIX и должен работать в любой современной оболочке. Кроме того, если $varне содержит по крайней мере три символа, ничего не\n печатается, кроме конечной ewline. Опять же, если вы хотите, чтобы это было напечатано в этом случае, вам нужен дополнительный шаг, такой как:

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

Таким образом, $last3он всегда пуст, только если $varсодержит 3 или менее байтов. И $varтолько когда-либо заменяется, $last3если $last3пусто или unset- и мы знаем, что это не unsetпотому, что мы просто установили его.

mikeserv
источник
Это довольно аккуратно +1. Помимо: по какой причине вы не цитируете printfстроки формата?
Джейсонвриан
Почему бы просто не использовать ${VARNAME:(-3)}(предполагая bash)?
DopeGhoti
1
Спасибо за разъяснение; имеет смысл, даже если это выглядит (для меня) немного странно ...
Jasonwryan
1
@ DopeGhoti - просто потому, что это предположение я почти никогда не делаю. Это работает так же хорошо, bashкак и в любой другой оболочке, требующей совместимости POSIX.
mikeserv
3
@odyssey - Проблема в том , cshэто не среди современных, POSIX-совместимых оболочек , которые я упоминаю здесь, к сожалению. Спецификация оболочки POSIX смоделирована после ksh, которая смоделировала себя после комбинации обоих cshи традиционных оболочек в стиле Борна. kshобъединяет в себе cshотличную функциональность управления заданиями и перенаправление ввода / вывода старого стиля Bourne. Он также добавил некоторые вещи - такие как концепции манипуляции со строками, которые я демонстрирую выше. cshНасколько я знаю, это вряд ли сработает в любом традиционном , извините.
mikeserv
7

Вы можете сделать это, но это немного ... чрезмерно

for s in unlimited 987654 123456789; do
    rev <<< $s | cut -c 1-3 | rev
done 
ted
654
789
Гленн Джекман
источник
3

Пуленепробиваемое решение для струн 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")

Или используйте:

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

предотвратить неправильную обработку данных.

Пример:

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

Выводит что-то вроде этого:

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

Не зависит от настроек локали (т.е. работает с LC_ALL=C). Bash, sed, grep, awk, revТребуется что - то вроде этого:LC_ALL=en_US.UTF-8

Общее решение:

  • Получать байты
  • Определить кодировку
  • Декодировать байты в символы
  • Извлечь персонажей
  • Кодировать символы в байты

Вы можете определить кодировку с помощью uchardet . Смотрите также связанные проекты .

Вы можете декодировать / кодировать с помощью Encode в Perl, кодеков в Python 2.7.

пример :

Извлеките последние три символа из строки utf-16le и преобразуйте эти символы в 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"
)

Смотрите также: perlunitut , Python 2 Unicode HOWTO

Евгений Верещагин
источник
echoваш пуленепробиваемый источник?
mikeserv
@mikeserv, decode/encodeмой пуленепробиваемый источник. Вычистил мой ответ.
Евгений Верещагин
Это также зависит от настроек локали, чтобы гарантировать его правильную работу, поскольку набор байтов может отражать разные символы в разных кодировках. Это «работает» LC_ALL=Cпотому, что это очень «тупой» параметр, но он может сломаться, когда вы попытаетесь передать строку UTF-8 в SHIFT-5 или строку SHIFT-5 в KOI8 и т. Д.
Martin Tournoij
@Carpetsmoker, спасибо. Не могли бы вы объяснить свой комментарий? Я полагаю, это perl -CAO -e 'print substr($ARGV[0], -3)'работает нормально. Aэлементы @ARGV должны быть строками, закодированными в UTF-8, OSTDOUT будет в UTF-8.
Евгений Верещагин
Похоже, вы говорите о назначенииutf8_str
Евгений Верещагин
1

Как насчет использования "expr" или "rev"?

Ответ, аналогичный ответу @ G-Man : у expr "$yourstring" : '.*\(...\)$' него тот же недостаток, что и у решения grep.

Хорошо известный трюк состоит в том, чтобы объединить «cut» с «rev»: echo "$yourstring" | rev | cut -n 1-3 | rev

gildux
источник
revРешение выглядит как Glenn Джекмана
Джефф Schaller
Вы правы @Jeff_Schaller: Я пропустил один из
Гленна
0

Получить размер строки с помощью:

size=${#STRING}

Затем получите подстроку последнего n символов:

echo ${STRING:size-n:size}

Например:

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

даст:

789
Esref
источник
0

tail -n 1 revisions.log | awk '{print substr ($ 0, 0, длина ($ 0) - (длина ($ 0) -13))}'

Если вы хотите напечатать первые тринадцать символов с самого начала

Анкит Вишвакарма
источник
-1

printf не будет работать, если в строке есть пробелы.

Ниже код для строки с пробелом

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

Nux

Саурабх
источник
Хм, если printfне работает, то вы делаете что-то очень неправильно.
Кусалананда
1
@Kusalananda: Основываясь на команде, которую показывает Саурабх, они попробовали printf $str(а не printf "$str"или printf '%s' "$str"). И да, printf $strэто очень неправильно. ( echo -n $strне намного лучше.)
G-Man говорит «Восстановить Монику»