Вы пишете, что «хотите работать с файлами и заходить в каталоги», поэтому переход к правильному ls
решению может оказаться преждевременным. Было бы полезно точно знать, что вы подразумеваете под «работой с файлами и переходом в каталоги», чтобы найти лучшее решение.
Вот пара общих случаев использования:
Рекурсивное управление отдельными файлами
Предположим, что вы хотите что-то сделать для каждого файла, соответствующего некоторому критерию, начиная с текущего каталога и продолжая в каждом подкаталоге.
Например: найдите количество строк каждого файла с .txt
расширением. Команда для получения количества строк из одного файла wc -l $filename
. (Если вы дадите ему несколько имен файлов, он выведет количество строк каждого из них, а затем итоговое значение.)
Так вот, как решить проблему с одним файлом - это всегда первый вопрос, на который вам нужно ответить, прежде чем вы сможете двигаться дальше, - но как сделать это рекурсивно для всех файлов? Эта часть проблемы решается с помощью find
команды Unix для обхода каталога.
find
может быть сложная команда для изучения в деталях, но для простых случаев, таких как это, это довольно легко. Первое, что нужно знать, это то, что каждая find
команда имеет следующий формат:
find DIR [PREDICATE, ..]
DIR
является начальным каталогом (для данного примера .
, который всегда является текущим рабочим каталогом). A PREDICATE
- это выражение, которое find
используется, чтобы либо решить, что делать дальше при рассмотрении файла или каталога, либо сделать что-то с этим файлом или каталогом.
Ниже приведен основной алгоритм find
: попробуйте первый (самый левый в командной строке) предикат текущего проверяемого элемента (файла или каталога). Если предикат равен true, попробуйте следующий предикат в командной строке. Продолжайте, пока все указанные предикаты не будут опробованы. Если предикат имеет значение false, прекратите работу с этим элементом и начните снова со следующего элемента (начиная снова с первого предиката).
Если проверяемый элемент является каталогом, то после того, как последний предикат был достигнут или предикат ложен, find
продолжается с элементами внутри каталога. Есть два основных исключения из этого:
-prune
Предикат может быть использован для селективного отключения этого; если -prune
предикат достигнут и текущий элемент является каталогом, или -maxdepth=N
Вариант (не предикат, он появляется перед DIR
в командной строке) может использоваться, чтобы ограничить, насколько глубоко find
будет искать; если текущий каталог на N
несколько уровней глубже, чем начальный каталог,
тогда в любом случае содержимое каталога (и под-содержимое, рекурсивно) не проверяются, и следующий элемент будет таким же, как если бы текущий элемент был файлом, а не каталогом.
Говоря о: если проверяемый элемент является файлом, «следующий элемент» является следующей записью в том же каталоге, или, если в каталоге нет элементов, текущий каталог «извлекается» и обработка продолжается со следующим элементом, каким бы ни был следующий элемент при входе в каталог.
Что означает «обработка предмета»? Это означает, что каждый предикат проверяется слева направо в командной строке до тех пор, пока один из них не станет ложным, или пока все не будут опробованы.
(На данный момент существует расхождение между некоторыми различными версиями find
. Во многих более новых версиях, таких как версия, найденная в Linux, если последний предикат является истинным и не был предикатом «действия», то find
предполагается, что вы хотели что- то сделать, поэтому он действует так, как будто -print
предикат был задан для распечатки пути. В более старых версиях find
это не так, и результат обработки такого элемента будет равен нулю.
Для иллюстрации: самая простая команда find .
без предикатов. В более новых вариантах find
это приведет к списку всех путей, начинающихся в текущем каталоге и рекурсивно прогрессирующих, пока все не будут напечатаны. В более старых вариантах выполнения find
та же команда будет выполняться так же долго (она должна рекурсивно проверять все файлы на соответствие - в данном случае несуществующим - предикатам), но абсолютно ничего не выведет .)
Прежде чем покинуть тему обработки предикатов, я отмечу, что мое объяснение до сих пор показало, что единственной возможностью для предикатов является И логическое их использование. Это не правда, потому что
- есть также
-o
предикат, который OR два предиката (на самом деле, есть -a
предикат AND, но это редко требуется, потому что, как я писал выше, это поведение по умолчанию); find
позволяет использовать круглые скобки (которые из-за правил экранирования обычно записываются \(
и \)
) группируют несколько предикатов в одно выражение; а также - есть оператор отрицания, который обычно пишется
\!
.
После всего этого мы можем вернуться к вопросу о том, как получить количество строк для каждого файла с .txt
суффиксом:
- Как уже упоминалось, команда для получения количества строк в файле
wc -l
. - Существует предикат, доступный для запуска команды в проверяемом файле
find
. Это -exec CMD ;
, включая точку с запятой (которая должна быть экранирована при необходимости), и в тексте CMD
заменит любое вхождение токена {}
на путь, который в настоящее время проверяется. - Другой предикат позволяет нам проверить суффикс файла:
-name PATTERN
. Так что в этом случае, когда нам нужны файлы с .txt
расширением, мы используем *.txt
наш шаблон.
Итак, зная все это, команда, которую мы можем написать:
find . -name '*.txt' -exec wc -l {} \;
(Мы используем кавычки вокруг *.txt
и обратную косую черту перед точкой с запятой, чтобы предотвратить интерпретацию этих символов оболочкой как специальных, чтобы они find
могли их видеть.) Это будет проверять счетчик строк каждого файла с таким рекурсивным именем.
Здесь есть небольшая складка, которую можно игнорировать в зависимости от контекста: что если у вас есть каталог с именем, оканчивающимся на что-то, заканчивающееся .txt
? Вы получите что-то вроде следующего:
$ find . -name '*.txt' -exec wc -l {} \; 42 ./myfile.txt wc: ./foo.txt: Is a directory 0 ./foo.txt 1 ./foo.txt/bar.txt
Чтобы исправить это, вы должны добавить еще один предикат, -type f
чтобы указывать find
делать -exec
предикат только для файлов, которые являются обычными текстовыми файлами:
$ find . -type f -name '*.txt' -exec wc -l {} \; 42 ./myfile.txt 1 ./foo.txt/bar.txt
(Вы можете задаться вопросом, имеет ли значение, -type f
появляется ли -name '*.txt'
предикат до или после предиката. Это не так, потому что каталоги всегда спускаются, если только нет -prune
или -maxdepth
присутствует, как упоминалось ранее.)
Обратите внимание, что выше это возможно при использовании ls
в сочетании с расширенными возможностями оболочек Bash или ЗШ. Но эти решения гораздо сложнее объяснить и получить правильное решение, поэтому я собираюсь предположить, что ваше упоминание ls
было преждевременным внедрением. (См. Проблему XY .)
Сбор списка файлов, а затем манипулирование ими вместе
Я упомянул, что, если дано более одного имени файла, wc -l
указывается количество файлов по каждому файлу, а затем общая сумма. Но вышеупомянутое решение не получило итоговую сумму, потому wc
что запускалось один раз для каждого имени файла *.txt
. Но что, если вы хотите этот общий итог?
В этом случае вы можете использовать ls
, но вы столкнетесь с проблемой: если любое из ваших имен файлов потенциально содержит пробелы или другие символы, которые являются специальными для оболочки, вы можете получить ошибку или даже непреднамеренно выполнить команду, которую вы не имели в виду к.
Итак, еще раз, лучше обратиться к find
. Более новые версии find
(в основном, те, которые я упоминал ранее, будут вставлены -print
для вас, если вы не указали их) имеют функцию для этого: используйте -exec
предикат, как и раньше, но вместо точки с запятой заканчивайте на плюс ( +
). Так:
$ find . -type f -name '*.txt' -exec wc -l {} \+ 42 ./myfile.txt 1 ./foo.txt/bar.txt 43 total
Для тех версий, в которых find
отсутствует эта функция, вы должны использовать ее find
вместе с другой программой xargs
. xargs
принимает свой ввод и запускает команду с вводом, заданным в качестве аргументов команды. Итак, вот как мы будем использовать его для репликации нашей первой команды:
$ find . -type f -name '*.txt' -print | xargs wc -l 42 ./myfile.txt 1 ./foo.txt/bar.txt 43 total
Эта команда все еще имеет проблему, хотя, если одно из имен файлов содержит пробел:
$ ls My Spacey File.txt foo.txt myfile.txt rakudo-info.md $ find . -type f -name '*.txt' -print | xargs wc -l 42 ./myfile.txt wc: ./My: No such file or directory wc: Spacey: No such file or directory wc: File.txt: No such file or directory 1 ./foo.txt/bar.txt 43 total
В этом случае wc
каждое слово в имени файла My Spacey File.txt рассматривается как отдельный аргумент. Чтобы исправить это, мы используем функцию find
и соответствующую функцию, xargs
которая использует нулевой символ ( \0
который недопустим в именах файлов) в качестве разделителя вместо новых строк:
$ find . -type f -name '*.txt' -print0 | xargs -0 wc -l 42 ./myfile.txt 1 ./My Spacey File.txt 1 ./foo.txt/bar.txt 44 total
-print0
Предикат говорит, find
чтобы отправить свой выход, ограниченный нулями; -0
вариант xargs
делает то же самое для его ввода.
Заключительная оговорка
Если у вас очень большое количество файлов или общее количество символов всех имен файлов в совокупности очень велико, вы можете столкнуться с пределами количества или размера аргументов, разрешенных системой. В этом случае оба -exec ... \+
предиката find
и xargs
будут разбивать список и выполнять команду несколько раз, чтобы каждое имя файла использовалось один раз.
В современных системах это ограничение достаточно велико, так что вам не нужно беспокоиться об этом, пока вы не попадете хотя бы в тысячи имен файлов.