Как отличить файл от каталога в выводе ls? Я хочу работать с файлами и идти в каталогах, однако, я просто получаю список имен их всех:
for i in ls B
do
echo $i
done
На странице man ls
вы можете увидеть, какие записи являются каталогами, используя
-F, --classify
append indicator (one of */=>@|) to entries
Так что если вы используете
for i in $(ls -F B) ; do
echo $i
done
Вы должны увидеть, что каталоги имеют /
appended, а другие файлы - нет.
Однако, если вы хотите спуститься в каталоги, может быть лучше использовать test
for f in $(ls B) ; do
if [ -d $f ] ; then
recurse_into_directory
elif [ -f $f ]
process_file
else
echo "$f: neither regular file nor directory"
fi
done
Вы пишете, что "хотите работать с файлами и переходить в каталоги", поэтому переход к ls
может оказаться преждевременным. Было бы полезно точно знать, что вы подразумеваете под "работой с файлами и переходом в каталоги", чтобы найти лучшее решение.
Вот пара общих случаев использования:
Предположим, что вы хотите что-то сделать для каждого файла, соответствующего некоторому критерию, начиная с текущего каталога и продолжая в каждом подкаталоге.
Например: найдите количество строк каждого файла с расширением .txt
. Команда для получения количества строк одного файла - wc -l $filename
. (Если вы дадите ему несколько имен файлов, он выведет количество строк каждого из них, а затем итоговое значение.)
Так вот, как решить проблему с одним файлом - это всегда первый вопрос, на который нужно ответить, прежде чем вы сможете двигаться дальше, - но как сделать это рекурсивно для всех файлов? Эта часть проблемы решается с помощью команды find
, команды Unix для обхода каталога.
Команда find
может быть сложной для изучения в деталях, но для простых случаев, таких как эта, это довольно просто. Первое, что нужно знать, это то, что каждая команда find
имеет следующий формат:
find DIR [PREDICATE, ..]
DIR
является начальным каталогом (для этого примера .
, Который всегда является текущим рабочим каталогом). PREDICATE
- это выражение, которое find
чтобы решить, что делать дальше при рассмотрении файла или каталога, или что- то сделать с этим файлом или каталогом.
Ниже приведен базовый алгоритм find
: попробуйте первый (самый левый в командной строке) предикат текущего проверяемого элемента (файла или каталога). Если предикат равен true, попробуйте следующий предикат в командной строке. Продолжайте, пока все указанные предикаты не будут опробованы. Если предикат имеет значение false, прекратите работу с этим элементом и начните снова со следующего элемента (начиная снова с первого предиката).
Если проверяемый элемент является каталогом, то после достижения последнего предиката или предиката false, find
продолжается с элементами внутри каталога. Есть два основных исключения из этого:
-prune
может использоваться, чтобы выборочно отключить это; если достигнут предикат -prune
и текущий элемент является каталогом, или-maxdepth=N
вариант (не предикат, он появляется перед DIR
в командной строке) может быть использован , чтобы ограничить , насколько глубоко find
будет искать; если текущий каталог N
или более уровней глубже, чем начальный каталог,
тогда в любом случае содержимое каталога (и под-содержимое, рекурсивно) не проверяются, и следующий элемент будет таким же, как если бы текущий элемент был файлом, а не каталогом.
Говоря о: если проверяемый элемент является файлом, "следующий элемент" является следующей записью в том же каталоге, или, если в каталоге нет элементов, текущий каталог "извлекается" и обработка продолжается со следующим элементом, каким бы ни был следующий элемент при входе в каталог.
Что означает "обработка предмета"? Это означает, что каждый предикат проверяется слева направо в командной строке до тех пор, пока один из них не станет ложным, или пока все не будут опробованы.
(На данный момент существует расхождение между некоторыми различными версиями find
. Во многих более новых версиях, таких как версия, найденная в Linux, если последний предикат имеет значение true и не был предикатом "action", то find
предполагает, что вы хотели что- то сделать, поэтому он действует так, как если бы предикат -print
был задан как путь для распечатки. В более старых версиях find
это было не так, и результат обработки такого элемента был бы равен нулю.
Для иллюстрации: самая простая команда find .
без предикатов. В более новых вариантах find
это приведет к списку всех путей, начинающихся в текущем каталоге и рекурсивно прогрессирующих, пока все не будут напечатаны. В более старых вариантах find
та же команда будет выполняться так же долго (она должна рекурсивно проверять все файлы на соответствие - в данном случае несуществующим - предикатам), но абсолютно ничего не выведет.)
Прежде чем покинуть тему обработки предикатов, я отмечу, что мое объяснение до сих пор показало, что единственной возможностью для предикатов является И логическое их использование. Это не правда, потому что
-o
который определяет два предиката OR (на самом деле, существует предикат -a
И, но это редко требуется, потому что, как я писал выше, это поведение по умолчанию);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
разбивают список и запускают команду несколько раз, чтобы каждое имя файла использовалось один раз.
В современных системах это ограничение достаточно велико, так что вам не нужно беспокоиться об этом, пока вы не попадете хотя бы в тысячи имен файлов.