Learning Platform
Глоссарий Troubleshooting
Урок 10.02 · 20 мин
Начальный
RedirectionAppendnoclobberFile descriptorsShell

Основные операторы перенаправления

$ 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 если файл существует и не пустой.

open(), read(), write() — системные вызовы файлового ввода-вывода

Под капотом: что в kernel

При выполнении command > file:

  1. Bash делает fork()
  2. В child:
    • int fd = open("file", O_WRONLY | O_CREAT | O_TRUNC, 0666) — открывает файл, kernel возвращает FD
    • dup2(fd, 1) — копирует fd в FD 1 (теперь stdout указывает на этот файл)
    • close(fd) — закрывает первый FD (мы его перенесли в FD 1)
  3. 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

Попробуй сам

  1. 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
  2. Read from file:
    wc -l /etc/passwd
    wc -l < /etc/passwd
    # Различие в выводе
  3. Custom FD:
    exec 3> /tmp/log3.txt
    echo "hello" >&3
    exec 3>&-
    cat /tmp/log3.txt
  4. 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.
Проверка знанийKnowledge check
Объясни как работает 'command > /tmp/out' с точки зрения kernel: какие syscalls делает bash перед запуском command? Что произойдёт если /tmp/out не существует и если он уже существует с данными?
ОтветAnswer
Bash делает следующую последовательность: (1) fork() — создаёт child процесс; (2) В child: int fd = open(\"/tmp/out\", O_WRONLY | O_CREAT | O_TRUNC, 0666). Флаги: O_WRONLY (только запись), O_CREAT (создать если не существует), O_TRUNC (обрезать до нуля если существует). Mode 0666 будет AND-ed с umask (обычно 0022) -> реальный mode 0644. (3) dup2(fd, 1) — копирует FD 'fd' в позицию FD 1 (stdout). Теперь FD 1 указывает на тот же inode-открытие что fd. (4) close(fd) — закрывает дубликат, потому что он больше не нужен. (5) execve(\"command\") — запускает программу. Stdout программы уже на /tmp/out. Если файл НЕ существовал — O_CREAT создаст с mode 0644 (после umask). Если существовал — O_TRUNC обрежет его до 0 байт ПЕРЕД запуском command, ДАЖЕ ЕСЛИ command ничего не выведет. Это причина почему 'cmd > existing-file' может уничтожить данные при ошибке: труncate происходит до execve(), отдельно от cmd. Защита: set -o noclobber (тогда > на existing вызовет ошибку shell) или использовать >> (O_APPEND вместо O_TRUNC) или явная проверка [ -f file ] && mv file file.bak перед >.

Главное

  • > — 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 ОБРЕЖЕТ файл до нуля до запуска команды — будь осторожен.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. В чём принципиальная разница между 'cmd > file' и 'cmd >> file'?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5