tee: T-fitting в pipeline
tee (от водопроводного fitting в форме T) — копирует stdin в файл И в stdout:
$ command | tee file
# command's stdout -> file AND видимо на экране (stdout tee)
Это даёт две вещи одновременно: запись в файл и возможность продолжить pipe. Базовое использование — видеть вывод длинной команды и сохранять её одновременно:
$ ./run-etl.sh | tee /tmp/etl-run.log
# Видишь все строки на экране, и они сохранены в /tmp/etl-run.log
Флаги tee
tee file— overwrite (default)tee -a file— append (важно для логов)tee file1 file2 file3— в несколько файлов одновременноtee -i file— ignore SIGINT (не падать на Ctrl+C — иногда полезно)
# Дублировать в два файла + на экран:
$ command | tee logs/today.log logs/all.log
tee внутри pipeline
Самая мощная фишка — прерывание pipeline для логирования:
$ command | tee full-output.log | grep ERROR
# full-output.log получает полный вывод
# на экран попадают только ERROR-строки
Это полезно: pipeline транформации, но lossless intermediate log. После выполнения у тебя есть полный лог для post-mortem.
# ETL с lossless checkpoint:
$ extract.py | tee raw_data.json | jq '.users[]' | psql -c "COPY users FROM stdin"
# raw_data.json — full backup, на случай если transform/load упали
tee и sudo
Классический use-case для tee — запись в root-only файлы из обычной shell:
# [X] НЕ работает: > обрабатывается shell-ом ДО sudo:
$ sudo echo "127.0.0.1 myhost" > /etc/hosts
bash: /etc/hosts: Permission denied
# Shell делает open(/etc/hosts) под твоим UID — fail
# [x] Работает: sudo применяется к tee, который пишет в файл:
$ echo "127.0.0.1 myhost" | sudo tee -a /etc/hosts
127.0.0.1 myhost # вывод tee на экран
$ tail -1 /etc/hosts
127.0.0.1 myhost # запись прошла
-a (append) обязателен — без него файл будет перезаписан, что обычно НЕ то, что ты хочешь для /etc/hosts.
Если не нужен echo от tee на экран:
$ echo "127.0.0.1 myhost" | sudo tee -a /etc/hosts > /dev/null
# Запись произошла, на экран ничего не выводится
tee и process substitution
Самая магическая возможность — разветвление pipeline на несколько направлений:
$ command \
| tee >(grep ERROR > errors.log) \
| tee >(grep WARN > warnings.log) \
| grep INFO > info.log
>(...) — process substitution: bash превращает команду в файл-аналог (FIFO под капотом). tee пишет stdin одновременно:
- в первый
>(grep ERROR > errors.log)— внутри запускается grep, который пишет в errors.log - в свой stdout (для следующего pipe)
Результат: данные одновременно идут в errors.log, warnings.log и info.log. Это fan-out без temp-файлов.
# Сразу несколько compressed-уровней:
$ command | tee >(gzip > backup.gz) >(xz > archive.xz) > raw.log
# raw.log — uncompressed
# backup.gz — gzip-сжатый
# archive.xz — xz-сжатый
# Все три за один проход данных через command
Here-docs (heredoc): multi-line input
<<EOF — heredoc, передаёт multi-line блок текста как stdin команды. Полезно для:
- Передачи multi-line строки в команду без файла
- Embed SQL в bash-скрипт
- Передачи Python/awk-программ в одну строку shell
$ cat <<EOF
Line 1
Line 2
Line 3
EOF
Line 1
Line 2
Line 3
<<EOF — bash читает следующие строки до строки EOF (на отдельной строке, без отступа в default mode), передаёт их как stdin команды.
EOF — это delimiter, можешь использовать любой identifier:
$ cat <<END
hello
END
$ cat <<MYBLOCK
content
MYBLOCK
Variable expansion в heredoc
$ NAME=alice
$ cat <<EOF
Hello, $NAME!
EOF
Hello, alice!
По default переменные $VAR и command substitution $(...) expanded. Если хочешь literal (без expansion) — quoted delimiter:
$ cat <<'EOF'
Hello, $NAME! This is a $literal string.
EOF
Hello, $NAME! This is a $literal string.
Quoted <<'EOF' сохраняет dollar-signs как литералы.
<<- для отступов
<<-EOF (с минусом) игнорирует leading TABS (только табы, не пробелы):
$ cat <<-EOF
indented line (tabs at start ignored)
another one
EOF
indented line (tabs at start ignored)
another one
Это удобно в bash-скриптах с indentation:
function setup_database() {
psql <<-EOF
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT
);
EOF
}
Tabs внутри скрипта (для читаемости) удаляются при передаче. Без - — текст имел бы tabs включёнными.
DE-сценарии heredoc
1. SQL внутри bash
COPY и INSERT в PostgreSQL — загрузка данных из файлов$ psql -d analytics <<EOF
CREATE TABLE staging_orders (
order_id INT,
user_id INT,
amount NUMERIC,
country TEXT
);
COPY staging_orders FROM '/tmp/orders.csv' WITH (FORMAT csv, HEADER);
SELECT COUNT(*) FROM staging_orders;
EOF
Заменяет .sql файлы для quick-and-dirty ETL.
2. Inline Python
$ python3 <<EOF
import json
data = json.load(open('/tmp/data.json'))
print(sum(item['amount'] for item in data))
EOF
3. Конфиг-файл из bash:
$ cat > /etc/myapp.conf <<EOF
host = $HOSTNAME
port = 5432
user = $USER
EOF
Создаёт конфиг с подстановкой переменных.
4. AWS CLI multi-step через heredoc
$ aws s3api put-bucket-policy --bucket my-bucket --policy "$(cat <<EOF
{
"Version": "2012-10-17",
"Statement": [...]
}
EOF
)"
JSON-policy inline в скрипте — нет нужды в отдельном файле.
Here-strings: <<< для одной строки
<<< — короткая версия heredoc для одной строки:
$ grep "alice" <<< "alice 30
bob 25
carol 28"
alice 30
$ wc -c <<< "hello"
6 # 5 chars + \n
$ bc <<< "2 + 2"
4
<<< принимает строку (после bash-expansion переменных), передаёт как stdin.
Полезно когда нужно передать переменную на stdin команды:
$ data="alice,30,USA"
$ awk -F',' '{print $1}' <<< "$data"
alice
Без <<<: echo "$data" | awk ... — то же, но с extra процессом echo.
Heredoc combined with redirect
$ cat > /tmp/script.py <<'PYEOF'
import sys
for line in sys.stdin:
print(line.upper(), end='')
PYEOF
# Создаёт /tmp/script.py с этим содержимым
$ cat >> /tmp/log.txt <<EOF
$(date) - Script started
$(date) - Processing
EOF
# Append-логирует с timestamps
Попробуй сам
- tee базовое:
ls /tmp | tee /tmp/files.txt | wc -l - tee fan-out:
echo "data" | tee >(tr a-z A-Z > /tmp/upper.txt) >(rev > /tmp/rev.txt) > /tmp/orig.txt cat /tmp/upper.txt /tmp/rev.txt /tmp/orig.txt - Heredoc:
cat <<EOF Hello $USER Today is $(date +%Y-%m-%d) EOF - Quoted heredoc (no expansion):
cat <<'EOF' $USER won't be expanded $(date) stays literal EOF - Here-string:
awk '{print $2}' <<< "alice 30 USA"
macOS-различия
teeидентичен на macOS и Linux.- Heredoc (
<<EOF,<<-EOF,<<'EOF') — POSIX, работает на bash, zsh, dash. - Here-string (
<<<) — bash/zsh extension, не POSIX! На/bin/sh(dash в Debian/Ubuntu) не работает. Для portable скриптов используйecho "$var" | cmdвместоcmd <<< "$var". - Process substitution
>(...)и<(...)— bash/zsh, не POSIX.
Главное
tee FILEкопирует stdin в FILE И в stdout.-aappend. Несколько файлов:tee f1 f2 f3.- Идиома
... | sudo tee FILEдля записи в root-owned файлы из non-root shell. tee >(cmd1) >(cmd2)— fan-out pipeline в несколько направлений (process substitution).- Heredoc
<<EOF ... EOF— multi-line stdin. Quoted<<'EOF'— без variable expansion. <<-EOF— leading tabs игнорируются (для красивых indented скриптов).- Here-string
cmd <<< "text"— одна строка как stdin. Bash/zsh-only. - Process substitution
>(...),<(...)— bash-only, не POSIX. - Используй heredoc для inline SQL/Python/JSON — заменяет необходимость отдельных файлов.