Кількість символів у виході команди оболонки


12

Я пишу сценарій, який повинен обчислити кількість символів у виході команди за один крок .

Наприклад, за допомогою команди readlink -f /etc/fstabслід повернутися, 10оскільки вихід цієї команди становить 10 символів.

Це вже можливо зі збереженими змінними за допомогою наступного коду:

variable="somestring";
echo ${#variable};
# 10

На жаль, використання тієї ж формули з рядком, створеним командою, не працює:

${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution

Я розумію, що це можна зробити, попередньо збереживши результат у змінній:

variable=$(readlink -f /etc/fstab);
echo ${#variable};

Але я хотів би зняти додатковий крок.

Чи можливо це? Сумісність з оболонкою Almquist (sh) з використанням лише вбудованих або стандартних утиліт.


1
Вихід readlink -f /etc/fstabстановить 11 символів. Не забудьте новий рядок. Інакше ви побачите, /etc/fstabluser@cern:~$ коли запустили його з оболонки.
Філ Мороз

@PhilFrost у вас, здається, є кумедний підказки, ви працюєте в CERN?
Дмитро Григор’єв

Відповіді:


9

З GNU expr :

$ expr length + "$(readlink -f /etc/fstab)"
10

+Є спеціальна особливість GNU , exprщоб переконатися , що наступний аргумент трактується як рядок , навіть якщо це трапляється, exprоператор , як match, length, +...

Вищезазначене позбавить будь-якого зворотного нового рядка виходу. Щоб обійти це:

$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10

Результат відняли до 2, оскільки остаточний новий рядок readlinkта символ, який .ми додали.

З рядком Unicode, exprздається, не працює, оскільки він повертає довжину рядка в байтах замість підрахунку символів (див. Рядок 654 )

$ LC_ALL=C.UTF-8 expr length ăaa
4

Отже, ви можете використовувати:

$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3

ПОСІБНО:

$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10

Простір перед заміною команд запобігає збоїв команди з рядком -, тому нам потрібно відняти 3.


Спасибі! Здається, що ваш третій приклад працює навіть без того LC_ALL=C.UTF-8, що значно спрощує речі, якщо кодування рядка не буде відомо заздалегідь.
користувач339676

2
expr length $(echo "*")- ніпе. Принаймні , використовувати подвійні лапки: expr length "$(…)". Але це позбавляє від команди нових рядків з команди, це неминуча особливість підстановки команд. (Ви можете обійти це, але тоді відповідь стає ще складнішою.)
Жил "ТАК - перестань бути злим"

6

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

  1. Ви можете використовувати wc -mкількість підрахунків символів. На жаль, він також нараховує остаточний новий рядок, тому вам доведеться позбутися цього першого:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. Можна, звичайно, використовувати awk

    readlink -f /etc/fstab | awk '{print length($0)}'
  3. Або Перл

    readlink -f /etc/fstab | perl -lne 'print length'

Ви маєте на увазі exprвбудований? У якій оболонці?
mikeserv

5

Я зазвичай роблю так:

$ echo -n "$variable" | wc -m
10

Для виконання команд я би адаптував це так:

$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10

Цей підхід схожий на те, що ви робили в два етапи, за винятком того, що ми поєднуємо їх в один вкладиш.


2
Ви повинні використовувати -mзамість -c. З символами unicode ваш підхід буде порушений.
cuonglm

1
Чому б не просто readlink -f /etc/fstab | wc -m?
Філ Мороз

1
Чому ви використовуєте цей ненадійний метод замість ${#variable}? Принаймні використовуйте подвійні лапки echo -n "$variable", але це все ж не вдається, якщо, наприклад, значення variableє -e. Коли ви використовуєте його в поєднанні з підстановкою команд, майте на увазі, що відключені нові рядки позбавлені.
Жил 'SO- перестань бути злим'

@philfrost b / c те, що я показав, було складене з того, що оп вже думав. Крім того, він працює для будь-яких cmds, які він, можливо, встановив попередньо у VARS і хоче, щоб їх довжина була завершена. Також тердон має такий приклад.
slm

1

Ви можете зателефонувати за зовнішніми утилітами (див. Інші відповіді), але вони зроблять ваш сценарій повільнішим, і важко правильно встановити сантехніку.

Зш

У zsh ви можете написати, ${#$(readlink -f /etc/fstab)}щоб отримати довжину підстановки команд. Зауважте, що це не довжина виводу команди, це довжина виводу без будь-якого зворотного нового рядка.

Якщо ви хочете точну довжину виводу, виведіть додатковий символ, що не є новим рядком, і відніміть його.

$((${#$(readlink -f /etc/fstab; echo .)} - 1))

Якщо вам потрібна корисна навантаження у висновку команди, тоді вам потрібно відняти два , оскільки вихід readlink -f- це канонічний шлях плюс новий рядок.

$((${#$(readlink -f /etc/fstab; echo .)} - 2))

Це відрізняється від ${#$(readlink -f /etc/fstab)}рідкісного, але можливого випадку, коли сам канонічний шлях закінчується новим рядком.

У цьому конкретному прикладі вам взагалі не потрібна зовнішня утиліта, оскільки zsh має вбудовану конструкцію, еквівалентну readlink -fчерез модифікатор історії A.

echo /etc/fstab(:A)

Щоб отримати довжину, використовуйте модифікатор історії в розширенні параметра:

${#${:-/etc/fstab}:A}

Якщо у вас є ім'я файлу в змінній filename, це було б ${#filename:A}.

Снаряди в стилі Борн / POSIX

Жодна з чистих оболонок Bourne / POSIX (Bourne, ash, mksh, ksh93, bash, yash…) не має подібного розширення, про яке я знаю. Якщо вам потрібно застосувати підстановку параметрів до виводу підстановки команди або до заміни параметрів гніздо, використовуйте послідовні етапи.

Ви можете залити обробку у функцію, якщо хочете.

command_output_length_sans_trailing_newlines () {
  set -- "$("$@")"
  echo "${#1}"
}

або

command_output_length () {
  set -- "$("$@"; echo .)"
  echo "$((${#1} - 1))"
}

але зазвичай користі немає; крім ksh93, це призводить до того, що додаткова вилка зможе використовувати вихід функції, тому він робить ваш сценарій повільнішим, і рідко є користь для читання.

Знову ж таки, результат readlink -f- це канонічний шлях плюс новий рядок; якщо ви хочете довжину канонічного шляху, віднімайте 2 замість 1 дюйма command_output_length. Використання command_output_length_sans_trailing_newlinesдає правильний результат лише тоді, коли сам канонічний шлях не закінчується новим рядком.

Байти проти символів

${#…}повинна бути довжина в символах, а не в байтах, що змінює багатобайтові локалі. Доцільно сучасні версії ksh93, bash та zsh обчислюють довжину в символах відповідно до значення LC_CTYPEна момент розширення ${#…}конструкції. Багато інших поширених оболонок насправді не підтримують багатобайтові локалі: починаючи з тире 0,5,7, mksh 46 та posh 0,12,3, ${#…}повертає довжину в байтах. Якщо ви хочете, щоб довжина символів була надійною, скористайтеся wcутилітою:

$(readlink -f /etc/fstab | wc -m)

Поки $LC_CTYPEпозначає дійсну локаль, ви можете бути впевнені, що це або помилиться (на стародавній або обмеженій платформі, що не підтримує багатобайтові локалі), або поверне правильну довжину символів. (Для Unicode "довжина символів" означає кількість точок коду - кількість гліфів - це ще одна історія, пов’язана з ускладненнями, такими як поєднання символів.)

Якщо ви хочете довжину в байтах, встановіть LC_CTYPE=Cтимчасово або використовуйте wc -cзамість цього wc -m.

Підрахунок байтів або символів wcвключає будь-які зворотні нові рядки з команди. Якщо ви хочете довжину канонічного шляху в байтах, це так

$(($(readlink -f /etc/fstab | wc -c) - 1))

Для отримання символів віднімайте 2.


@cuonglm Ні, вам потрібно відняти 1. echo .додає два символи, але другий символ - це останній новий рядок, який позбавлений підстановки команд.
Жил "ТАК - перестань бути злим"

Новий рядок є з readlinkвиводу плюс .мінус echo. Ми обоє погоджуємось, що echo .додаємо два символи, але проривається новий рядок. Спробуйте printf .або перегляньте мою відповідь unix.stackexchange.com/a/160499/38906 .
cuonglm

@cuonglm Питання задало кількість символів у виході команди. Результатом readlinkє ціль посилання плюс новий рядок.
Жил "ТАК - перестань бути злим"

0

Це працює, dashале це вимагає, щоб цільовий var був безумовно порожнім або не встановлено. Ось чому це насправді дві команди - я явно порожню $lв першій:

l=;printf '%.slen is %d and result is %s\n' \
    "${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"

ВИХІД

len is 10 and result is /etc/fstab

Ось і всі вбудовані оболонки - не враховуючи, readlinkзвичайно, - але оцінюючи це в поточній оболонці, це означає, що ви повинні виконати завдання перед тим, як отримати len, тому я %.sвиконую перший аргумент у рядку printfформату та просто додаю його знову для буквальне значення в кінці printfсписку arg.

З eval:

l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\\n "$l"

ВИХІД

10:/etc/fstab

Ви можете наблизитись до тієї ж речі, але замість виводу змінної у першій команді ви отримаєте це на stdout:

PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"

... що пише ...

10:/etc/fstab

... подати дескриптор 1 без присвоєння будь-якого значення будь-якій варі в поточній оболонці


1
Чи не саме цього хотіла уникнути ОП? "Я розумію, що це можна зробити, попередньо збереживши результат у змінній: variable=$(readlink -f /etc/fstab); echo ${#variable};Але я хотів би видалити додатковий крок."
тердон

@terdon, напевно, я неправильно зрозумів, але склалося враження, що крапка з комою - це проблема, а не змінна. Ось чому вони отримують len і виводять в одній простій команді, використовуючи лише вбудовані оболонки. Оболонка не EXEC readlink потім Exec expr, наприклад. Це, мабуть, має значення лише в тому випадку, якщо якимось чином вимкнене значення len окулює значення, яке, я визнаю, мені складно зрозуміти, чому це може бути, але я підозрюю, що може бути випадок, у якому він мав значення.
mikeserv

1
evalСпосіб, до речі, є , ймовірно , самим чистим тут - він призначає вихід і Лен до того ж імені вару в одному виконанні - дуже близький до цього l=length(l):out(l). Робити expr length $(command) чи закупорити значення на користь Len, до речі.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.