Как удалить все директории `node_modules`

Директория node_modules занимает довольно много места на жёстком диске, и если вы не работаете активно над проектом, то её можно спокойно удалить — благодаря package.json вы получите те же (или совместимые) версии пакетов через npm install. Но если проектов много, то удалять руками утомительно. Если вы на Маке/Линуксе, то это легко делегировать компьютеру, запустив в терминале эту команду в директории, где лежат проекты:

find . -type d -name 'node_modules' -prune | xargs -I% rm -rf %

Как обычно, в интернетах никто не даёт гарантий, что это не взорвёт ваш компьютер, rm удаляет файлы мимо Корзины и отменить его нельзя. (Но если в node_modules только нодовские модули, то всё должно быть ок — у вас же есть package.json)

На днях я так безболезненно освободил больше 15 гигабайт на ноутбуке, как-то поднакопились старые проекты с тоннами зависимостей.

В принципе всё, это весь пост. Если любопытно, дальше я немного расскажу про то, как это работает — но если вы уже знаете про пайпы и xargs, то вряд ли узнаете что-то новое.

Как это работает

Здесь я использую одновременно четыре программы: шелл, в котором запускаю команду (он настраивает передачу данных между find и xargs), find (чтобы найти все папки с названием node_modules ), xargs (чтобы превратить стандартный ввод в аргумент для rm -rf), и rm (ну, чтобы удалять файлы).

Про шелл нужно поговорить отдельно, но пока достаточно знать, что это программа, которая читает то, что я написал, и запускает соответствующие программы.

find

Команда find предназначена для поиска файлов, метаданные которых (название, дата изменения, создания, владелец, и тому подобное) соответствуют заданным правилам. Программа существует с незапамятных времён, она очень мощная и гибкая, и поэтому её синтаксис довольно запутанный (до широкого распространения утилит, написанных на Go, find был единственной используемой мной программой, которой был нужен один дефис в длинных аргументах, а не обычные два — что, конечно, мучительно).

Я предпочитаю делать необходимый минимум через find, потому что я не могу запомнить все её опции и действия. Но давайте разберём это заклинание по частям: find . -type d -name 'node_modules' -prune. . означает текущую директорию, откуда мы начинаем поиск. -type d означает, что мы ищем не файлы, а директории. -name 'node_modules' — что название директории должно быть node_modules. Пока всё вроде логично! Но что такое -prune?

По умолчанию find рекурсивно заходит во все директории, а внутри node_modules частенько бывают другие node_modules, поэтому мы легко можем получить такой вывод:

$ find . -type d -name 'node_modules'
/home/mt/super_secret/node_modules
/home/mt/super_secret/node_modules/express/node_modules
..ещё пять тысяч строк..

Но тогда, если пытаться удалять в том же порядке, то я получу ошибку на строчке с expressrm её уже удалил.

Опция -prune заставляет find исключить директории, подошедшие под условия, из рекурсивного поиска, то есть, не заходить внутрь. Так я получу каждую директорию node_modules верхнего уровня, без ненужных «дублей».

По умолчанию find просто выводит найденные файлы и ничего с ними не делает, для этого я использую xargs. На самом деле, настоящие знатоки Юниксов раскритиковали бы мою изначальную команду, потому что удалить все папки node_modules можно и одним вызовом find, без дополнительных программ и ненужной передачи данных между ними, но так я получил желаемый результат быстрее. Я могу попробовать несколько вариантов команды find без rm, убедиться, что всё выглядит правильно, и только затем добавить rm. Не очень эффективно с точки зрения компьютерных ресурсов, но компьютер и так гораздо быстрее меня, так что пусть пройдётся по файлам и два, и три раза.

xargs и пайпы

xargs — тоже древняя программа со слегка эзотерическим синтаксисом. Она превращает стандартный ввод в вызовы других команд. Это кажется довольно абстрактным, но именно поэтому она часто бывает полезна. Но чтобы в этом разобраться, стоит сделать шаг назад.

Юникс-системы построены вокруг текста. Один из важных кирпичиков в их фундаменте — возможность передачи данных между программами через пайпы: это когда вывод одной команды подаётся на ввод другой команде. Этим занимается шелл (помимо запуска самих команд). Из-за того, что между программами ходит текст, программам не нужно ничего знать друг про друга, поддерживать специальный API или ещё что-то. Так, например, есть команда sort, которая сортирует стандартный ввод и отдаёт его в стандартный вывод. Нам не нужно добавлять сортировку в код каждой программы, вывод которой мы хотим отсортировать — достаточно перенаправить его в sort:

$ du -h | sort -h
# рекурсивно найти, сколько места занимают файлы | отсортировать
$ awk -f useragent.awk access.log | sort | uniq -c
# вызвать awk-скрипт | отсортировать | посчитать повторения

Конечно, каждый конкретный случай можно решить не через пайпы, а написав специализированную программу (попросту скрипт на питоне или джаваскрипте), но когда освоишься с шеллом и часто используемыми утилитами, то обычно проще набросать пайплайн, чем идти в редактор кода создавать новый файл. Собственно, xargs — одна из этих утилит.

Проблема в том, что команда rm не принимает список файлов к удалению через стандартный ввод, куда придёт список файлов из find. Если бы была rm_from_stdin, то можно было бы сделать find <...> | rm_from_stdin. xargs позволяет сымпровизировать такую команду. Разберём по частям xargs -I% rm -rf %. -I% означает, что я хочу использовать % в качестве заменяющего символа, то бишь, перед тем как запустить команду, нужно заменить все % на строчку из стандартного ввода. А дальше идёт команда: rm -rf %. Тут флаги -rf уже предназначены для самой rm, и означают recursive и force (удалять без подтверждений).

Зачем знать это всё

Наверно, в npm есть специальный пакет, удаляющий все папки с node_modules. Возможно, что есть программы для массового удаления директорий с графическим интерфейсом. Но я часто сталкиваюсь с задачами, которые чуть-чуть, да не вписываются в рамки того, что предугадали авторы удобных графических программ. Юникс-утилиты позволяют решать такие проблемы на лету, не особо задумываясь, итеративно. Эти утилиты отточены десятилетиями использования и улучшения (см. как GNU yes выдаёт огромное количество y в секунду).

Несомненно, каждую такую задачу можно решить, написав кастомную программу — но из юникс-утилит, как из конструктора, можно собрать решение из готовых кирпичиков. Представьте, сколько строчек и скобочек бы заняло решение на джаваскрипте.

При этом пайплайны сложно растить, скажем, когда нужно координировать много процессов или логика становится более запутанной. В этом же ключе интересно разбираться с перлом: его легко начать использовать без церемоний, писать небольшие одноразовые скрипты, а при необходимости — перенести уже написанное в редактор кода и писать Как Правильно.

Роб Пайк как-то отметил, что Юникс мёртв, а Пёрл читал речь на похоронах.