Learning Platform
Глоссарий Troubleshooting
Урок 10.05 · 28 мин
Средний
xargsProcess substitutionParallel executionfind -execPipes

Проблема: команды, не читающие stdin

Большинство команд читают stdin (grep, sort, wc, awk, sed). Но многие — нет (rm, cp, mv, ls, mkdir). Они ждут аргументы в командной строке.

# Не работает — rm не читает stdin:
$ find /tmp -name '*.tmp' | rm

# Не работает — cp не читает stdin:
$ ls *.csv | cp /backup/

Решение — xargs. Он читает stdin, собирает токены в arguments командной строки следующей команды и запускает.

$ find /tmp -name '*.tmp' | xargs rm
# xargs читает stdin построчно (или по whitespace), строит:
#   rm /tmp/file1.tmp /tmp/file2.tmp /tmp/file3.tmp
# и запускает

Базовый синтаксис

$ command_producing_args | xargs cmd_to_run [initial_args]

xargs читает stdin и добавляет токены после cmd_to_run [initial_args]:

$ echo "file1 file2 file3" | xargs ls -l
ls -l file1 file2 file3

$ echo "file1\nfile2\nfile3" | xargs touch
touch file1 file2 file3

xargs batches аргументы автоматически — если их слишком много (превышает ARG_MAX, обычно ~2 MB на Linux), xargs разделит на несколько вызовов:

# 100,000 файлов? Не проблема — xargs запустит rm batches:
$ find /tmp -name '*.tmp' | xargs rm
# xargs: rm /tmp/file1 ... /tmp/file9000
# xargs: rm /tmp/file9001 ... /tmp/file18000
# (batches на ~9000 файлов)

-I : placeholder

По default xargs добавляет аргументы в конец команды. Если нужно подставить в середину — используй -I:

$ ls /tmp/*.csv | xargs -I {} cp {} /backup/
# xargs запускает для каждого:
#   cp /tmp/file1.csv /backup/
#   cp /tmp/file2.csv /backup/
#   ... (по одному вызову на файл, не batch)

$ ls *.tar | xargs -I {} tar -xf {} -C /tmp/extracted

-I {} означает «использовать {} как placeholder». Сам символ {} — convention, можно использовать любой:

$ ls *.csv | xargs -I FILE mv FILE archive/

Когда нужен -I:

  • Файл нужен в середине команды
  • Нужно несколько раз использовать аргумент: xargs -I {} cp {} {}.bak
  • Нужны вокруг аргумента ещё параметры: xargs -I {} tar -xf {} -C /dest

Минус -I: по одному вызову на token (нет batching). Медленнее для большого числа файлов. Без -I xargs batches optimally.

-0 / -print0: NUL-separator для safe-with-spaces

По default find выводит имена файлов через \n, а xargs читает по whitespace (space, tab, newline). Это ломается на файлах с пробелами или quotes:

$ touch "/tmp/file with spaces.csv"
$ find /tmp -name '*.csv' | xargs ls
ls: cannot access '/tmp/file': No such file or directory
ls: cannot access 'with': ...
ls: cannot access 'spaces.csv': ...
# xargs trato "file with spaces.csv" как ТРИ separate arguments

Решение — использовать NUL-character (\0) как separator:

$ find /tmp -name '*.csv' -print0 | xargs -0 ls
# -print0: find выводит имена разделённые \0 (не \n)
# -0: xargs читает \0-separated
# \0 не может быть в имени файла (Linux запрещает) — гарантия safety

-0 (или --null) — всегда используй в production-скриптах с find и xargs. Безопасно даже с самыми гнусными именами.

DE-сценарии xargs

1. Удалить старые файлы

$ find /var/log/etl -name '*.log.*' -mtime +30 -print0 | xargs -0 rm
# Удалить .log.* файлы старше 30 дней

2. Параллельная обработка

xargs -P N запускает N процессов одновременно:

$ ls *.csv | xargs -P 4 -I {} python3 process.py {}
# 4 параллельных Python-процесса

Это classical способ горизонтального scaling на одной машине. На 16-core CPU — xargs -P 16 использует все ядра.

# Compress all logs параллельно:
$ find /var/log -name '*.log' -mtime +1 -print0 | xargs -0 -P 8 gzip
# 8 параллельных gzip

Без -P 8 это последовательно (один gzip на файл). С -P 8 — параллельно, в 8 раз быстрее на multi-core.

3. Find + grep recursive

$ find /opt -name '*.py' -print0 | xargs -0 grep 'TODO'
# Эквивалент grep -r TODO /opt/*.py
# Но xargs allows extra grep flags, parallel, etc.

4. Удалить только если матчит pattern

$ ls /tmp/*.csv | xargs -I {} sh -c 'wc -l {} | awk "\$1 == 0 {print \$2}"' | xargs rm
# Удалить пустые CSV

Альтернативы xargs

find -exec

# Эквивалент xargs:
$ find . -name '*.tmp' -exec rm {} +
# Плюс в конце — batching как xargs (без +, отдельный rm на файл)

$ find . -name '*.tmp' -exec rm {} \;
# Точка-с-запятой — отдельный exec на каждый файл (медленно для multi)

# С действием в середине:
$ find . -name '*.csv' -exec cp {} /backup/ \;

-exec ... +xargs. -exec ... \;xargs -I {} (по одному вызову).

parallel (GNU parallel)

$ ls *.csv | parallel python3 process.py
$ ls *.csv | parallel -j 4 python3 process.py {}

GNU parallel — мощнее xargs (выводы interleave-free, более удобный syntax, лучшая отчётность), но не всегда установлен. Для cross-platform xargs -P достаточно.

Process substitution: <(...) и >(...)

Process substitution — bash-механизм, превращающий вывод/вход команды в файл-аналог (FIFO). Это то, что использует tee >(grep ...).

<(...) — output команды как input файл

# Команда требует "файл" аргументом, но у нас данные с stdin/команды:
$ diff <(sort file1) <(sort file2)
# diff видит два "файла" — каждый это вывод sort
# Не нужно создавать temp-файлы

$ comm -12 <(sort -u list1) <(sort -u list2)
# Intersection отсортированных уникальных списков

Под капотом bash:

  1. Создаёт FIFO /dev/fd/63 (или похожий).
  2. Запускает sort file1, направляет вывод в FIFO.
  3. Передаёт /dev/fd/63 как аргумент diff.
  4. diff читает «файл» = FIFO = вывод sort.
# Сравнить вывод двух команд:
$ diff <(ssh prod 'cat /etc/myapp.conf') <(cat /local/myapp.conf)
# Diff config production vs local

>(...) — input команды как output файл

Обратное: использует команду как destination для записи. Пример уже видели в уроке 04:

$ command | tee >(grep ERROR > err.log) > all.log
# tee пишет в "файл" /dev/fd/63 (FIFO), на другой стороне которого grep

DE-pipeline с process substitution

1. Сравнение dataset

SQL SELECT — как получить данные для bash-сравнения
$ diff <(psql -c 'SELECT * FROM today_orders' | sort) <(psql -c 'SELECT * FROM yesterday_orders' | sort)

Diff между двумя SQL-запросами без temp-файлов.

2. Multiple inputs для команды

$ paste <(cut -d ',' -f 1 names.csv) <(cut -d ',' -f 3 ages.csv) > combined.tsv

Combine две колонки из разных CSV.

3. Lossless tee + transform

$ curl -s api.com/data \
    | tee >(jq '.metadata' > metadata.json) \
    | jq '.records[]'

Сохранить metadata в файл, продолжить pipeline с records.

Когда что использовать

xargs vs process substitution vs find -exec

Каждый инструмент в своей нише.

xargsпревратить stdin в args следующей командыИспользуй для команд, не читающих stdin (rm, cp, mv). С -P для параллелизации. Базовый toolkit.
find -exec ... +как xargs, но интегрировано в findИдиоматично для find-based pipelines. Часто читаемее чем 'find ... | xargs'. + для batching, \\; для one-at-a-time.
process substitution <(...)команда как 'input файл'Когда другая команда требует file argument, не stdin. diff/comm/join на лету сгенерированных данных.
process substitution >(...)команда как 'output файл'С tee для fan-out pipeline в несколько направлений.
GNU paralleladvanced параллелизацияКогда xargs -P не хватает (interleaved output, complex job control).

Попробуй сам

  1. Базовый xargs:
    echo "file1 file2 file3" | xargs touch
    ls file*
    rm file*
  2. Безопасный с -0:
    touch "/tmp/a b c.txt"
    find /tmp -name '*.txt' -print0 | xargs -0 ls -l | head
    rm "/tmp/a b c.txt"
  3. Параллелизация:
    seq 1 10 | xargs -P 4 -I {} sh -c 'echo "Job {}"; sleep 0.5'
    # 10 jobs в 4 параллельных потоках
  4. Process substitution с diff:
    echo -e "a\nb\nc" > /tmp/f1
    echo -e "a\nB\nc" > /tmp/f2
    diff <(sort /tmp/f1) <(sort /tmp/f2)
  5. comm с inline sort:
    echo -e "apple\nbanana\ncherry" > /tmp/f1
    echo -e "banana\ncherry\ndate" > /tmp/f2
    comm -12 <(sort /tmp/f1) <(sort /tmp/f2)
    # Intersection: banana, cherry

macOS-различия

  • xargs идентичен на macOS и Linux для базовых флагов (-I, -0, -n).
  • xargs -P на macOS поддерживается (BSD xargs).
  • find -print0 поддерживается (BSD find).
  • Process substitution <(...), >(...) — bash/zsh feature. Работает на macOS если shell — bash или zsh (default).
  • На pure /bin/sh или dash (Debian default для скриптов) — process substitution не работает.
Проверка знанийKnowledge check
У тебя в директории /var/log/etl 50000 .log.gz файлов старше 30 дней, которые нужно удалить. Какая команда оптимальная и почему? Сравни 'find ... -delete', 'find ... -exec rm {} \;', 'find ... -exec rm {} +' и 'find ... -print0 | xargs -0 rm'.
ОтветAnswer
Оптимальные — варианты 1, 3, 4 (одинаково эффективные); вариант 2 (-exec ... \;) медленный. Анализ: (1) find ... -delete — встроенная find-команда удаления, не fork'aет внешний rm. Самый быстрый: один процесс, минимум context switches. (2) find ... -exec rm {} \; — точка-с-запятой = ОТДЕЛЬНЫЙ rm-процесс на КАЖДЫЙ файл. 50000 fork() + 50000 execve() + 50000 wait() = десятки секунд лишнего overhead. Плохо. (3) find ... -exec rm {} + — плюс = BATCHING, find собирает имена в batches до ARG_MAX (~2MB) и вызывает rm с many arguments. 50000 файлов -> несколько rm вызовов, скажем 6-10. Быстро. (4) find ... -print0 | xargs -0 rm — то же что вариант 3 по сути: NULL-разделители для safety + xargs batches. Чуть медленнее find -delete (extra process), но сравнимо с -exec +. На 50000 файлов: -delete ~2-3 sec; -exec + ~3-5 sec; xargs ~3-5 sec; -exec \; — 60+ sec. Правило: для massive deletion — -delete оптимально. Для других ops где нет find built-in — -exec + или xargs. Никогда -exec \; для bulk operations. -print0 + xargs -0 — критично для file names с пробелами/quotes.

Главное

  • xargs превращает stdin в arguments следующей команды. Без него — rm, cp, mv не работают с pipe.
  • -I {} — placeholder для middle-of-command substitution. Минус: один вызов на token.
  • -0find -print0) — NUL-separated для safety с file names содержащими пробелы.
  • -P N — N параллельных процессов. Простой horizontal scaling.
  • find ... -exec ... +xargs (batching), find ... -exec ... \;xargs -I (one-at-a-time).
  • Process substitution <(cmd) — команда как input file (для diff, comm, join, paste).
  • Process substitution >(cmd) — команда как output file (для tee fan-out).
  • Process substitution — bash/zsh, не POSIX.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Зачем нужен xargs?

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

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

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

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