Основные операторы перенаправления
$ command > file # stdout -> file (overwrite, truncate to 0)
$ command >> file # stdout -> file (append)
$ command < file # stdin ← file
$ command 2> file # stderr -> file
$ command 2>> file # stderr -> file (append)
$ command &> file # stdout + stderr -> file (bash only)
$ command &>> file # stdout + stderr -> file, append (bash only)
$ command > out 2> err # stdout -> out, stderr -> err (раздельно)
$ command > file 2>&1 # stdout -> file, stderr -> туда же (КАНОН)
> vs >> : overwrite vs append
$ echo "first" > log.txt
$ echo "second" > log.txt
$ cat log.txt
second # 'first' стёрт
$ echo "first" >> log.txt
$ echo "second" >> log.txt
$ cat log.txt
first
second # Оба сохранены
> буквально открывает файл с флагом O_TRUNC — обрезает до нуля перед записью. Это безусловно. Даже если команда не написала ничего, файл будет обнулён:
$ echo "important data" > file.txt
$ true > file.txt # true ничего не выводит, но > сработал!
$ wc -c file.txt
0 file.txt # [X] Данные потеряны
Будь осторожен с > file в скриптах. >> (append) безопаснее для логов.
noclobber: защита от случайного overwrite
$ set -o noclobber # включить защиту
$ echo "important" > log.txt
$ echo "new" > log.txt
bash: log.txt: cannot overwrite existing file
# Force overwrite через >|
$ echo "new" >| log.txt
$ cat log.txt
new
noclobber опция в bash превращает > на existing файлы в ошибку. Чтобы намеренно overwrite — >| (с pipe).
Включи в .bashrc для интерактивной защиты:
# ~/.bashrc
set -o noclobber
Но в скриптах NOclobber может неожиданно ломаться (если ожидаешь overwrite) — лучше не включать глобально, а быть осторожным с > в опасных местах.
< : чтение из файла
$ cat < file.txt # cat читает из file.txt
$ wc -l < data.csv # wc читает из data.csv
# Сравни:
$ wc -l data.csv
1247 data.csv # выводит имя файла
$ wc -l < data.csv
1247 # только число (стdin не имеет имени)
< file менее распространён, потому что многие команды принимают имя файла как аргумент: wc -l file.csv и wc -l < file.csv делают почти то же. Разница: первый знает имя файла (печатает в выводе), второй — нет.
Когда < важен:
# while-loop читает построчно из файла
$ while IFS= read -r line; do
echo "Got: $line"
done < input.txt
# Команда принимает только stdin, не файл:
$ md5sum < secret.txt # эквивалент md5sum secret.txt — но без вывода имени
Несколько перенаправлений одновременно
$ command > out.log 2> err.log < input.txt
# stdin ← input.txt
# stdout -> out.log
# stderr -> err.log
Порядок в записи неважен (это не sequential как dup2). Все три перенаправления применяются перед execve(command).
Дублирование FD: >&
$ command > file 2>&1 # stderr -> stdout, потом stdout -> file
$ command >file >&2 # сначала stdout -> file, потом stdout -> копия stderr??
Второй вариант — confusing. >&2 = «duplicate stdout to FD 2», то есть stdout пишет туда же, куда указывает FD 2 (терминал по умолчанию).
# Поменять местами stdout и stderr через временный FD:
$ command 3>&1 1>&2 2>&3 3>&-
# Это: открыть FD 3 как копию stdout; stdout -> stderr; stderr -> FD 3 (то что было stdout); закрыть FD 3.
# Финально: stdout и stderr поменялись местами.
Это редко нужно, но иллюстрирует мощь FD-manipulation в bash.
Открытие custom FD: exec
# Открыть FD 3 для записи в файл:
$ exec 3> /tmp/log.txt
# Теперь любая запись в FD 3 идёт в этот файл:
$ echo "hello" >&3
# В конце скрипта закрыть FD 3:
$ exec 3>&-
Полезно для скриптов с несколькими parallel logs:
#!/bin/bash
exec 3> debug.log
exec 4> errors.log
exec 5> data.log
echo "Debug info" >&3
echo "Error: $thing" >&4
echo "$record" >&5
Это альтернатива открывать-закрывать файлы каждый echo (быстрее, никаких race conditions).
DE-сценарии redirection
1. Логирование с timestamps
# Каждая строка с timestamp в log:
$ command 2>&1 | ts '%Y-%m-%d %H:%M:%S' >> /var/log/etl/run.log
# ts — утилита из пакета moreutils, добавляет timestamp каждой строке
2. Append-only логи для аудита
$ command >> /var/log/audit.log 2>&1
# Все запуски кумулятивно сохраняются
3. Daily лог-файл
$ command > /var/log/etl/$(date +%Y%m%d).log 2>&1
# Сегодня — в файл с today's дате. Завтра — в новый файл.
4. Backup data перед overwrite
$ [ -f data.csv ] && cp data.csv data.csv.bak
$ ./regenerate.sh > data.csv
# Сначала бэкап, потом перезапись
5. Cron-job с conditional logging
# Логирует только если есть ошибки:
$ command > /tmp/output 2> /tmp/errors
$ if [ -s /tmp/errors ]; then
cat /tmp/errors >> /var/log/etl/errors.log
fi
[ -s file ] — true если файл существует и не пустой.
Под капотом: что в kernel
При выполнении command > file:
- Bash делает
fork() - В child:
int fd = open("file", O_WRONLY | O_CREAT | O_TRUNC, 0666)— открывает файл, kernel возвращает FDdup2(fd, 1)— копирует fd в FD 1 (теперь stdout указывает на этот файл)close(fd)— закрывает первый FD (мы его перенесли в FD 1)
execve("command")— запускает программу с уже перенаправленным stdout
При >> file — флаг O_APPEND вместо O_TRUNC:
open("file", O_WRONLY | O_CREAT | O_APPEND, 0666);
O_APPEND гарантирует atomic-append: даже если две программы пишут в один файл, их writes не interleave. Kernel перед каждым write делает lseek(fd, 0, SEEK_END) атомарно с write. Это критично для multi-process logging.
Чтение нескольких файлов: heredoc и multiple inputs
$ command < file1 < file2
# Только file2 будет stdin — последний < побеждает
# Несколько файлов через cat:
$ cat file1 file2 | command
# Heredoc — про это в уроке 04
$ command <<EOF
multiline
input
EOF
Попробуй сам
- Overwrite vs append:
echo "a" > /tmp/test.log echo "b" > /tmp/test.log cat /tmp/test.log # -> b (overwrite) echo "a" >> /tmp/test.log echo "b" >> /tmp/test.log cat /tmp/test.log # -> b, a, b - Read from file:
wc -l /etc/passwd wc -l < /etc/passwd # Различие в выводе - Custom FD:
exec 3> /tmp/log3.txt echo "hello" >&3 exec 3>&- cat /tmp/log3.txt - noclobber test:
echo "x" > /tmp/test set -o noclobber echo "y" > /tmp/test # должно упасть echo "y" >| /tmp/test # force overwrite set +o noclobber
macOS-различия
- Все базовые операторы (
>,>>,<,2>) — POSIX, работают идентично. &>и&>>— bash-only, не POSIX. На macOS работают если shell это bash (или zsh).noclobber(set -o noclobber) — POSIX, работает везде.- Custom FD через
exec— POSIX.
Главное
>— overwrite (O_TRUNC),>>— append (O_APPEND). Atomic-append для multi-process logs.<— чтение из файла. Используется для while-read-loops и команд только-stdin.2>для stderr,2>>append.&>и&>>— bash-shortcut для обоих.set -o noclobber— защита от accidentral overwrite. Force через>|.exec 3> file— открыть custom FD для записи в файл. Закрытьexec 3>&-.- Под капотом: bash делает
fork()+open() + dup2() + close()ДОexecve(). > existingОБРЕЖЕТ файл до нуля до запуска команды — будь осторожен.