Читаем файл построчно в 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 ушел по пизде
Такие дела, изучай.