Директория 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
..ещё пять тысяч строк..
Но тогда, если пытаться удалять в том же порядке, то я получу ошибку на строчке с express
— rm
её уже удалил.
Опция -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
в секунду).
Несомненно, каждую такую задачу можно решить, написав кастомную программу — но из юникс-утилит, как из конструктора, можно собрать решение из готовых кирпичиков. Представьте, сколько строчек и скобочек бы заняло решение на джаваскрипте.
При этом пайплайны сложно растить, скажем, когда нужно координировать много процессов или логика становится более запутанной. В этом же ключе интересно разбираться с перлом: его легко начать использовать без церемоний, писать небольшие одноразовые скрипты, а при необходимости — перенести уже написанное в редактор кода и писать Как Правильно.
Роб Пайк как-то отметил, что Юникс мёртв, а Пёрл читал речь на похоронах.