Как в 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, поскольку echo в пустой строке печатает новую строку.
Эндрю Нгуен
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}")
lucifurious
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. Я добавил это как альтернативный ответ, чтобы включить результаты тестов. Смотри ниже.
Stilez
23

Другой способ использования здесь строк в bash:

wc -l <<< "$var"

Как упоминалось в этом комментарии , пустой $varрезультат приведет к 1 строке вместо 0 строк, потому что здесь строки добавляют символ новой строки в этом случае ( объяснение ).

говорящий
источник
1
Я должен был сделать это, чтобы получить правильный ответ: wc -l <<<"$(echo "$var")"(да, каждый символ был необходим)
Николай С.
@NicolaiS Значит, ты сделал что-то не так. В чем было содержание $var?
спикер 08
1
@NicolaiS Это правильно , потому что ваш varсодержит одну строку : Вы не интерпретируют \nни с чем. Поместите несколько строк в ваш, varи он будет работать, например, с var="foo<ENTER>bar<ENTER>baz"<ENTER>.
Speaker 09
2
xxd <<< ''создайте этот шестнадцатеричный дамп 00000000: 0a. Следовательно, <<<(Here Strings) добавляет символ новой строки к любому содержимому.
MiniMax
1
@MiniMax Спасибо, я добавил ваш вклад в свой ответ. Вы можете найти объяснение такого поведения здесь .
спикер
9

Вы можете заменить «wc -l» на «wc -w», чтобы лучше подсчитывать количество слов, а не строк. Это не будет считать новые строки и может использоваться для проверки того, пусты ли ваши исходные результаты, прежде чем продолжить.

Кристофер Брансдон
источник
wc -lsolutions выводит 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[@]}
haqk
источник
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
автобонус
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по умолчанию делает то, что мы хотим. В качестве бонуса использование using printfсохраняет процесс, поскольку это встроенная оболочка. (Предложение: printf "$a" | wc -lболее лаконично и исключает ненужное использование grep)
joshtch
@joshtch Ну .. нет. Как сказали другие в этом обсуждении, printf .... | wc -lбудет удалена только одна дополнительная строка (новая строка), так что в случае пустой строки результат будет 0 . Верный. Но если мы передадим 2 строки, результатом будет 1, где та же переменная, переданная в printf ... | grep "^"объект, вернет правильно 2. Кроме того, использование напрямую printf "$a"очень опасно, потому что это может привести к тихим ошибкам, если строка случайно содержит такие символы, как %s, %dи так on ... То же самое, если строка начинается с тире. Вместо этого параметр 2 в printfавтоматически экранируется
Люк Сэйвфрогс,
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 (восстановление после модификации, сложно использовать с ограниченной областью действия для внутренних команд, ...)
адренохром
источник
1

Чтобы избежать имени файла в команде "wc -l":

lines=$(< "$filename" wc -l)
echo "$lines"
user3503711
источник