Читаем файл построчно в Bash
Довольно часто нужно быстро загрузить файл построчно в массив.
Как делает мальчик:
lines=()
while IFS= read -r line; do
lines+=("$line")
done < file.txt
echo "Первая строка: ${lines[0]}"
echo "Всего строк: ${#lines[@]}"
Как делает мужчина:
mapfile -t lines < file.txt
echo "Первая строка: ${lines[0]}"
echo "Всего строк: ${#lines[@]}"
В первом варианте много кода, НО, работает везде. Во втором варианте, работает только в bash ≥4.0, но кода в разы меньше и не жрет CPU.
Совет: если пишешь скрипт под bash — всегда используй
mapfile
. Если нужен кросс-шелл (sh
,dash
,ash
) — оставайся на цикле.
Либо расширь второй вариант и укажи:
#!/usr/bin/env bash
Это гарантирует, что твой скрипт выполнится именно через bash, а не через системный sh
(который может быть dash
, ash
, ksh
и т.п.).
env
ищет bash в $PATH
, так что это более переносимо, чем жёстко указывать #!/bin/bash
Ну и прицепом можешь добавить: set -euo pipefail
Это включение «строгого режима» в баше:
-e
— выход из скрипта при любой ошибке (не игнорировать exit code ≠ 0).
-u
— ошибка при обращении к неинициализированной переменной (не будет пустых значений «по-тихому»).
-o pipefail
— пайплайны возвращают код ошибки первой упавшей команды, а не последней.
По итогу:
- Скрипт точно запустится под bash
- Ошибки не будут замалчиваться
- Сразу ловишь косяки
Удобно в CI/CD, где всё должно падать быстро и без хуйни.
grep foo file.txt | wc -l
echo $? # 0, даже если grep ничего не нашёл
set -o pipefail
grep foo file.txt | wc -l
echo $? # 1, потому что grep ушел по пизде
Такие дела, изучай.