Команда для друку лише останніх 3 символів рядка


30

Я знаю, що cutкоманда може надрукувати перші nсимволи рядка, але як вибрати останні nсимволи?

Якщо у мене є рядок зі змінною кількістю символів, як я можу надрукувати лише три останні символи рядка. напр.

Необхідний необмежений вихід "Тед"
Необхідний вихід "987654" - "654"
Необхідний вихід "123456789" - "789"

Відповіді:


52

Чому ніхто не дав очевидної відповіді?

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

… Або трохи менш очевидним

grep -o '...$'

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


6
абоgrep -o '.\{3\}$'
Avinash Raj

3
абоecho "unlimited" | python -c "print raw_input()[-3:]"
Кіро

8
@Kiro або "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уникає необхідності уникати брекетів.
Raghu Dodda

43

Зберігаючи це просто - хвіст

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

$ printf 123456789 | tail -c 3
789

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

Справжні символи Unicode?

Тепер ви запитаєте останні три символи ; Це не те, що дає ця відповідь: вона виводить три останні байти !

Поки кожен символ є одним байтом, він tail -cпросто працює. Тому його можна використовувати, якщо набір символів є ASCII, ISO 8859-1або варіант.

Якщо у вас є введення Unicode, як у загальному UTF-8форматі, результат неправильний:

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

У цьому прикладі з використанням UTF-8грецьких символів альфа, бета та гама є двома байтами:

$ printf 123αβγ | wc -c  
9

Параметр -mможе принаймні рахувати реальних символів unicode:

printf 123αβγ | wc -m
6

Добре, тож останні 6 байт дадуть нам останні три символи:

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

Отже, tailне підтримує обробку загальних символів і навіть не намагається (див. Нижче): Він обробляє лінії змінного розміру, але не містить символів змінного розміру.

Поставимо це так: tailпідходить для вирішення структури проблеми, але неправильно для типу даних.

Основні елементи GNU

Дивлячись далі, то виходить, що 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.


Дивіться також цей відповідь , щоб не можете використовувати `вирізати -c` (` --characters`) з UTF-8? .


2
Насправді, більшість інших відповідей, здається, справляються з Unicode просто чудово, доки поточна локаль задає кодування UTF-8. Тільки cutрішення, що базуються на вашому та Гленна Джекмана, не здаються.
Ільмарі Каронен

@IlmariKaronen Щоправда, дякую за підказку. Я відредагував, із деякими додатковими подробицями.
Волкер Зігель

1
Зауважте, що POSIX прямо вказує, що tailмає працювати з байтами, а не символами. Я колись зробив патч, щоб додати новий параметр для вибору символів, але я вважаю, що ніколи не зливався: - /
Martin Tournoij

Не працює у файловому режимі, як-от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]}"

2
Як називається такий вид синтаксису / операції, щоб я міг шукати більше інформації?
Тулен Кордова

6
Це називається Розширення підрядків . Це свого роду розширення параметрів . Загальна форма - $ {параметр: offset: length} , але поле довжини необов’язкове (і, як ви бачите, у відповіді вище було опущено). DopeGhoti також може писати ${STRING:(-3):3}(із зазначенням поля довжини ), ${STRING: -3}(з пробілом між і :та -), або ${STRING: -3:3}.
G-Man каже: "Відновіть Моніку"

У цьому випадку вказівка ​​довжини 3є дещо суперечливою, оскільки це вимагає "трьох символів від третього від останнього символу включно", що в практичному відношенні є ідентичною операції "Усі символи вперед від третього від останнього , включно ".
DopeGhoti


11

Якщо рядок є змінною, ви можете зробити:

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

Це позбавляє останніх трьох символів зі значення $varтипу:

${var%???}

... а потім знімає з голови $varвсе, окрім того, що було лише знято:

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

Цей метод має свої переваги та недоліки. Зі свого боку він повністю POSIX-портативний і повинен працювати в будь-якій сучасній оболонці. Крім того, якщо $varне містить щонайменше трьох символів, не\n друкується нічого, окрім кінцевої лінії ewline. Потім, якщо ви хочете, щоб він був надрукований у такому випадку, вам потрібен додатковий крок, наприклад:

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

Цей спосіб $last3порожній завжди, лише якщо $varмістить 3 або менше байт. І $varтільки коли - або замінити , $last3якщо $last3порожня або unset- і ми знаємо , що це не unsetтому , що ми просто встановити його.


Це досить охайний +1. Убік: з якої причини ви не цитуєте свої printfрядки формату?
Jasonwryan

Чому б не просто використовувати ${VARNAME:(-3)}(припускаючи bash)?
DopeGhoti

1
Дякую за уточнення; має сенс, навіть якщо це виглядає (як на мене) трохи дивно ...
Jasonwryan

1
@DopeGhoti - просто тому, що це припущення, яке я майже ніколи не роблю. Це добре працює, як і в bashбудь-якій іншій оболонці, яка вимагає сумісності POSIX.
mikeserv

3
@odyssey - Проблема в тому , cshце НЕ серед сучасних, POSIX-сумісних оболонок , які я згадую тут, на жаль. Моделюється специфікація оболонки POSIX ksh, яка моделюється як поєднання обох, так cshі традиційних оболонок у стилі Борна. kshвключив як cshвідмінний функціонал управління роботою, так і перенаправлення старих стилів Борна. Він також додав деякі речі - наприклад, принципи маніпулювання струнами, які я демонструю вище. Напевно, це, напевно, не спрацює в жодному традиційному 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 . Дивіться також пов'язані проекти .

Ви можете розшифрувати / кодувати за допомогою кодування в 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" : '.*\(...\)$' Він має той самий недолік, що і греп-рішення.

Добре відомий трюк - поєднувати "cut" з "rev": echo "$yourstring" | rev | cut -n 1-3 | rev


revРішення виглядає як Glenn Джекмана
Джефф Schaller

Ви маєте рацію @Jeff_Schaller: Я пропустив один з
glenn

0

Отримайте розмір рядка за допомогою:

size=${#STRING}

Потім отримайте підрядку останнього n символу:

echo ${STRING:size-n:size}

Наприклад:

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

дав би:

789

0

хвіст -n 1 ревізії.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 каже "Відновити Моніку"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.