У bash, як мені порахувати кількість рядків у змінній?


82

У мене є змінна, в якій зберігається рядок, і мені потрібно перевірити, чи є в ній рядки:

var=`ls "$sdir" | grep "$input"`

псевдокод:

while [ ! $var's number of lines -eq 1 ]
  do something

Це моя ідея, як це перевірити. echo $var | wc -lне працює - це завжди говорить 1, хоч і має 3.

echo -e не працює так само добре.

Відповіді:


108

Котирування мають значення.

echo "$var" | wc -l

3
Це не лапки навколо команди, це лапки навколо заміни команди. Пари цитат не заважатимуть.
Ігнасіо Васкес-Абрамс,

38
Це має тонкощі. Порожній рядок повертає 1, оскільки луна на порожньому рядку друкує новий рядок.
Ендрю Нгуєн,

3
Іншими словами: if [ -z "$var" ]; then printf '%s\n' '0'; else printf '%s\n' "${var%$'\n'}" | wc -l; fi. Спробуйте з var=(без рядків) var='foo'та var=$'foo\n'(обидва рядки у значенні * nix).
l0b0

2
І ще один спосіб LINE_COUNT=$(wc -l <<< "${var}")
перевести

4
@Tim echo -nпросто зменшить кількість одиниць . @lucifurious echo $(wc -l <<< "${NONEXISTENTVAR}")все одно дає 1, а не 0
Джуліан

73

Прийнята відповідь та інші відповіді, розміщені тут, не працюють у випадку порожньої змінної (невизначений або порожній рядок).

Це працює:

echo -n "$VARIABLE" | grep -c '^'

Наприклад:

ZERO=
ONE="just one line"
TWO="first
> second"

echo -n "$ZERO" | grep -c '^'
0
echo -n "$ONE" | grep -c '^'
1
echo -n "$TWO" | grep -c '^'
2

Я не можу відтворити те, що звучить як помилка з @PolyTekPatrick, тобто WHITESPACE_ONE=' 'в моїй оболонці (bash) все ще працює, правильно повідомляючи один рядок.
Джуліан

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

4
Ідеально! Це має бути прийнятою відповіддю. Наразі це єдине рішення, яке належним чином відповідає на питання у всіх випадках. Дякуємо, що показали тестові кейси для підтвердження цього.
PolyTekPatrick

4
ТАК! Я теж вважаю, що це має бути прийнятою відповіддю.
Martin Joiner

1
Це правильно, але можна спростити, printfскоріше, ніж echo -n. Я додав це як альтернативну відповідь, щоб включити результати тесту. Дивись нижче.
Стілез

23

Інший спосіб використання тут рядків у bash:

wc -l <<< "$var"

Як згадувалося в цьому коментарі , порожнє $varпризведе до 1 рядка замість 0 рядків, оскільки тут рядки додають символ нового рядка в цьому випадку ( пояснення ).


1
Мені довелося зробити це, щоб отримати правильну відповідь: wc -l <<<"$(echo "$var")"(так, кожен символ був необхідний)
Nicolai S

@NicolaiS Тоді ви зробили щось не так. Що було змістом $var?
спікер

1
@NicolaiS Це правильно, тому що ваш varмістить один рядок : Ви ні \nз чим не інтерпретували . Вставте кілька рядків у ваш файл, varі він буде працювати, наприклад, з var="foo<ENTER>bar<ENTER>baz"<ENTER>.
спікер

2
xxd <<< ''створити цей hexdump 00000000: 0a. Отже, <<<(Here Strings) додає символ нового рядка до будь-якого вмісту.
MiniMax

1
@MiniMax Дякую, я додав ваш внесок до своєї відповіді. Пояснення цій поведінці можна знайти тут .
спікер

9

Ви можете замінити "wc -l" на "wc -w", а не рахувати кількість слів замість рядків. Це не враховуватиме жодних нових рядків і може бути використано для перевірки, чи не порожні початкові результати, перш ніж продовжувати.


wc -lрішення виводить 1, навіть якщо вхідна змінна порожня, тому wc -wбуде краще використовувати.
Кадір

9

Ніхто не згадував розширення параметрів, тож ось кілька способів використання чистого bash.

Спосіб 1

Видаліть символи, що не є рядками, потім отримайте довжину рядка + 1. Важливі лапки .

 var="${var//[!$'\n']/}"
 echo $((${#var} + 1))

Спосіб 2

Перетворити на масив, потім отримати довжину масиву. Щоб це працювало, не використовуйте лапки .

 set -f # disable glob (wildcard) expansion
 IFS=$'\n' # let's make sure we split on newline chars
 var=(${var})
 echo ${#var[@]}

2
Спосіб 2 чистіший і, можливо, швидший. Як би він не покладався на IFS. Отже, встановіть, IFS=$'\n'щоб переконатись, що змінна розділена на нові рядки при розширенні її в масив:IFS=$'\n'; var=(${var})
untore

Мені подобається метод 2 через його мінімальні накладні витрати: жодних зовнішніх команд і навіть вбудованого не видно. Це також цілком читабельно.
DKroot

1
Проте ShellCheck скаржиться: github.com/koalaman/shellcheck/wiki/SC2206 . Це на щось. set -fнеобхідний, щоб уникнути небажаного розширення глобуса. Спробуйте використовувати метод 2 далі var=$'*\n.*'.
DKroot

авто-бонус для чистого внутрішнього Баш без зовнішньогоwc -l
nhed

7

Простіша версія відповіді @ Julian, яка працює для всіх рядків, із завершенням чи без \ n (він вважає файл, що містить лише один кінцевий \ n, порожнім):

printf "%s" "$a" | grep -c "^"

  • Повертає нуль: не встановлена ​​змінна, порожній рядок, рядок, що містить оголений новий рядок
  • Повертає 1: будь-який непустий рядок, із або без завершення нового рядка
  • тощо

Вихід:

# a=
# printf "%s" "$a" | grep -c "^"
0

# a=""
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf "")"
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf "\n")"
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf " \n")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf " ")"
# printf "%s" "$a" | grep -c "^"
1

# a="aaa"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s" "aaa")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s\n" "aaa")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s\n%s" "aaa" "bbb")"
# printf "%s" "$a" | grep -c "^"
2

# a="$(printf "%s\n%s\n" "aaa" "bbb")"
# printf "%s" "$a" | grep -c "^"
2

+1. Передача прапорів echo(наприклад, echo -n) не є стандартною і може давати різні результати для різних реалізацій. Тоді як printfробить те, що ми хочемо за замовчуванням. Як бонус, використання printfекономить ваш процес, оскільки це вбудована оболонка. (Пропозиція: printf "$a" | wc -lбільш стисла і уникає зайвого використання grep)
joshtch

@joshtch Ну .. ні. Як говорили інші в цій дискусії, printf .... | wc -lбуде видалено лише один зайвий рядок (новий рядок), щоб у випадку порожнього рядка результат був 0 . Правильно. Але якщо ми передамо 2 рядки, результат буде 1, де та сама змінна, передана printf ... | grep "^"волі, правильно поверне 2. Крім того, використання безпосередньо printf "$a"дуже небезпечно, оскільки це може призвести до тихих помилок, якщо рядок випадково містить символи типу %s, %dі так on ... Те саме, якщо рядок починається з тире. Натомість параметр 2 в printfавтоматичному режимі екранований
Luke Savefrogs

3

Відповіді, які проголосували найкраще, не вдаються, якщо grep не повертає результатів.

Homer Simpson
Marge Simpson
Bart Simpson
Lisa Simpson
Ned Flanders
Rod Flanders
Todd Flanders
Moe Szyslak

Це неправильний спосіб зробити це :

wiggums=$(grep -iF "Wiggum" characters.txt);
num_wiggums=$(echo "$wiggums" | wc -l);
echo "There are ${num_wiggums} here!";

Там нам скажуть, що у списку є 1 Віггум , навіть якщо таких немає.

Натомість вам потрібно зробити ще одну додаткову перевірку, щоб перевірити, чи порожня змінна ( -zяк, наприклад, "дорівнює нулю"). Якщо grep нічого не повернув, змінна буде порожньою.

matches=$(grep -iF "VanHouten" characters.txt);

if [ -z "$matches" ]; then
    num_matches=0;
else
    num_matches=$(echo "$matches" | wc -l);
fi

echo "There are ${num_matches} VanHoutens on the list";

2

Інший метод підрахунку кількості рядків у змінній - якщо припустити, що ви перевірили, чи вона успішно заповнена, або вона не порожня, для цього просто перевірте $? після афектування результату підподібної оболонки var -:

readarray -t tab <<<"${var}"
echo ${#tab[@]}

readarray | mapfile - це внутрішня команда bash, яка перетворює вхідний файл або тут рядок у цьому випадку в масив на основі нових рядків.

-t прапорець запобігає збереженню нових рядків у кінці комірок масиву, корисно для подальшого використання збережених значень

Перевагами цього методу є:

  • відсутність зовнішньої команди (wc, grep, ...)
  • відсутність оболонки (труби)
  • відсутність проблем з IFS (відновлення після модифікації, хитро використовувати з обмеженою командою сферою внутрішніх команд, ...)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.