Как POSIX-ли считать количество строк в строковой переменной?

Проблема решена, потребовалось

update control {
  ...
}

, теперь вроде все нормально.

10
20.11.2018, 08:40
3 ответа

Простой ответ заключается в том, что wc -l <<< "${string_variable}"является сокращением ksh/bash/zsh для printf "%s\n" "${string_variable}" | wc -l.

На самом деле существуют различия в том, как <<<и конвейер:<<<создают временный файл, который передается в качестве входных данных для команды, тогда как |создает конвейер. В bash и pdksh/mksh (, но не в ksh93 или zsh ), команда с правой -стороны канала выполняется в подоболочке. Но эти различия не имеют значения в данном конкретном случае.

Обратите внимание, что с точки зрения подсчета строк предполагается, что переменная не пуста и не заканчивается новой строкой. Не заканчивается символом новой строки в том случае, когда переменная является результатом подстановки команд, поэтому в большинстве случаев вы получите правильный результат, но вы получите 1 для пустой строки.

Есть два различия между var=$(somecommand); wc -l <<<"$var"иsomecommand | wc -l:с использованием подстановки команд и временной переменной, которая удаляет пустые строки в конце, забывая, закончилась ли последняя строка вывода новой строкой или нет (всегда так если команда выводит допустимый непустой текстовый файл ), и пересчет на единицу, если вывод пустой. Если вы хотите сохранить и результат, и количество строк, вы можете сделать это, добавив некоторый известный текст и удалив его в конце:

output=$(somecommand; echo.)
line_count=$(($(printf "%s\n" "$output" | wc -l) - 1))
printf "The exact output is:\n%s" "${output%.}"
11
27.01.2020, 20:01

Строка <<<здесь -в значительной степени представляет собой однострочную версию -документа <<здесь -. Первое не является стандартной функцией, а второе — есть. В этом случае вы также можете использовать <<. Они должны быть эквивалентны:

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

Хотя обратите внимание, что оба добавляют дополнительную новую строку в конце $somevar, например. это печатает 6, хотя переменная имеет только пять строк:

s=$'foo\n\n\nbar\n\n'
wc -l <<< "$s"

С помощью printfвы можете решить, нужен ли вам дополнительный перевод строки или нет:

printf "%s\n" "$s" | wc -l         # 6
printf "%s"   "$s" | wc -l         # 5

Но тогда обратите внимание, что wcсчитает только полные строки (или количество символов новой строки в строке ). grep -c ^также должен считать последний фрагмент строки.

s='foo'
printf "%s" "$s" | wc -l           # 0 !

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

(Конечно, вы также можете полностью подсчитать строки в оболочке, используя расширение ${var%...}для удаления их по одной в цикле...)

1
27.01.2020, 20:01

В тех удивительно частых случаях, когда вам действительно нужно обработать все не-пустые строки внутри переменной некоторым образом (, включая их подсчет ), вы можете установить IFS просто на новую строку, а затем использовать механизм разделения слов оболочки, чтобы разбить непустые строки -.

Например, вот небольшая функция оболочки, которая суммирует не -пустые строки во всех предоставленных аргументах:

lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)

Скобки, а не фигурные скобки, используются здесь для формирования составной команды для тела функции. Это заставляет функцию выполняться в подоболочке, чтобы она не загрязняла переменную IFS внешнего мира и настройку расширения пути при каждом вызове.

Если вы хотите перебрать не -пустые строки, вы можете сделать это аналогичным образом:

IFS='
'
set -f
for line in $lines
do
    printf '[%s]\n' $line
done

Такой способ манипулирования IFS часто -упускают из виду, он также удобен для таких вещей, как синтаксический анализ путей, которые могут содержать пробелы, из столбцов, разделенных табуляцией -. Тем не менее, вы должны знать, что преднамеренное удаление символа пробела, обычно включенного в стандартную настройку IFS пробела -вкладка -новая строка, может привести к отключению разделения слов в тех местах, где вы обычно ожидаете его увидеть.

Например, если вы используете переменные для создания сложной командной строки для чего-то вроде ffmpeg, вы можете включить -vf scale=$scaleтолько в том случае, если для переменной scaleзадано значение, не -пустое. Обычно вы можете добиться этого с помощью ${scale:+-vf scale=$scale}, но если IFS не включает свой обычный символ пробела во время расширения этого параметра, пробел между -vfи scale=не будет использоваться в качестве разделителя слов, а ffmpegбудет передан весь -vf scale=$scaleкак один аргумент, который он не поймет.

Чтобы исправить это, вам нужно либо убедиться, что IFS настроен более нормально, прежде чем выполнять расширение ${scale}, либо выполнить два расширения:${scale:+-vf} ${scale:+scale=$scale}.Разделение слов, которое оболочка выполняет в процессе начального анализа командных строк, в отличие от разделения, которое она выполняет на этапе расширения обработки этих командных строк, не зависит от IFS.

Еще кое-что, на что стоит потратить время, если вы собираетесь делать подобные вещи, — это создание двух глобальных переменных оболочки для хранения только табуляции и только новой строки:

t=' '
n='
'

Таким образом, вы можете просто включать $tи $nв расширения, где вам нужны табуляции и новые строки, вместо того, чтобы засорять весь код пробелами в кавычках. Если вы предпочитаете вообще избегать пробелов в кавычках в оболочке POSIX, в которой нет другого механизма для этого, может помочь printf, хотя вам нужно немного повозиться, чтобы обойти удаление завершающих символов новой строки в расширениях команд:

nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}

Иногда установка IFS, как если бы это была переменная окружения -команды, работает хорошо. Например, вот цикл, который считывает путь, который может содержать пробелы, и коэффициент масштабирования из каждой строки входного файла с разделителями табуляции -:

while IFS=$t read -r path scale
do
    ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt

В этом случае встроенная функция readвидит, что IFS настроена только на табуляцию, поэтому она не будет разбивать строку ввода, которую считывает, на пробелы. НоIFS=$t set -- $linesне работает :оболочка расширяется $linesпо мере построения setвстроенных аргументов перед выполнением команды, поэтому временная настройка IFS таким образом, что применяется только во время выполнения встроенной функции, которая приходит слишком поздно. Вот почему фрагменты кода, которые я привел выше, выделяют IFS в отдельный шаг и почему они должны решать проблему ее сохранения.

0
27.01.2020, 20:01

Теги

Похожие вопросы