Как отличить файл от каталога в выводе ls?

1358
Kosarar

Как отличить файл от каталога в выводе ls? Я хочу работать с файлами и заходить в каталоги, однако, я просто получаю список имен их всех:

for i in ls B  do echo $i done 
2
как минимум вы должны использовать `ls -l` Mokubai 7 лет назад 2
В общем, попытка [разобрать `ls` - плохая идея] (http://mywiki.wooledge.org/ParsingLs). Он имеет тенденцию разбиваться на имена файлов с пробелами, или они выглядят как глобусы. 8bittree 7 лет назад 0

2 ответа на вопрос

2
user4556274

На lsстранице руководства вы можете увидеть, какие записи являются каталогами, используя

 -F, --classify append indicator (one of */=>@|) to entries 

Так что если вы используете

for i in $(ls -F B) ; do echo $i done 

Вы должны увидеть, что каталоги /добавлены, а другие файлы - нет.


Однако, если вы хотите спуститься в каталогах, может быть лучше использовать 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`, используя` for f в B / *; вместо do в начале вашего примера `test`. 8bittree 7 лет назад 1
Отличный ответ! Не могли бы вы сказать мне, как проверить, соответствует ли имя файла некоторому шаблону? Я пробовал `elif [-f $ f] -and -regex". * $ $ Patern. * "; тогда` но это никогда не правда ... Kosarar 7 лет назад 0
http://superuser.com/questions/1183035/how-do-i-check-if-the-name-of-some-file-matches-some-pattern-has-sss-in-it-f Kosarar 7 лет назад 0
0
Trey

Вы пишете, что «хотите работать с файлами и заходить в каталоги», поэтому переход к правильному lsрешению может оказаться преждевременным. Было бы полезно точно знать, что вы подразумеваете под «работой с файлами и переходом в каталоги», чтобы найти лучшее решение.

Вот пара общих случаев использования:

Рекурсивное управление отдельными файлами

Предположим, что вы хотите что-то сделать для каждого файла, соответствующего некоторому критерию, начиная с текущего каталога и продолжая в каждом подкаталоге.

Например: найдите количество строк каждого файла с .txtрасширением. Команда для получения количества строк из одного файла wc -l $filename. (Если вы дадите ему несколько имен файлов, он выведет количество строк каждого из них, а затем итоговое значение.)

Так вот, как решить проблему с одним файлом - это всегда первый вопрос, на который вам нужно ответить, прежде чем вы сможете двигаться дальше, - но как сделать это рекурсивно для всех файлов? Эта часть проблемы решается с помощью findкоманды Unix для обхода каталога.

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

find DIR [PREDICATE, ..] 

DIRявляется начальным каталогом (для данного примера ., который всегда является текущим рабочим каталогом). A PREDICATE- это выражение, которое findиспользуется, чтобы либо решить, что делать дальше при рассмотрении файла или каталога, либо сделать что-то с этим файлом или каталогом.

Ниже приведен основной алгоритм find: попробуйте первый (самый левый в командной строке) предикат текущего проверяемого элемента (файла или каталога). Если предикат равен true, попробуйте следующий предикат в командной строке. Продолжайте, пока все указанные предикаты не будут опробованы. Если предикат имеет значение false, прекратите работу с этим элементом и начните снова со следующего элемента (начиная снова с первого предиката).

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

  1. -pruneПредикат может быть использован для селективного отключения этого; если -pruneпредикат достигнут и текущий элемент является каталогом, или
  2. -maxdepth=NВариант (не предикат, он появляется перед DIRв командной строке) может использоваться, чтобы ограничить, насколько глубоко findбудет искать; если текущий каталог на Nнесколько уровней глубже, чем начальный каталог,

    тогда в любом случае содержимое каталога (и под-содержимое, рекурсивно) не проверяются, и следующий элемент будет таким же, как если бы текущий элемент был файлом, а не каталогом.

Говоря о: если проверяемый элемент является файлом, «следующий элемент» является следующей записью в том же каталоге, или, если в каталоге нет элементов, текущий каталог «извлекается» и обработка продолжается со следующим элементом, каким бы ни был следующий элемент при входе в каталог.

Что означает «обработка предмета»? Это означает, что каждый предикат проверяется слева направо в командной строке до тех пор, пока один из них не станет ложным, или пока все не будут опробованы.

(На данный момент существует расхождение между некоторыми различными версиями find. Во многих более новых версиях, таких как версия, найденная в Linux, если последний предикат является истинным и не был предикатом «действия», то findпредполагается, что вы хотели что- то сделать, поэтому он действует так, как будто -printпредикат был задан для распечатки пути. В более старых версиях findэто не так, и результат обработки такого элемента будет равен нулю.

Для иллюстрации: самая простая команда find .без предикатов. В более новых вариантах findэто приведет к списку всех путей, начинающихся в текущем каталоге и рекурсивно прогрессирующих, пока все не будут напечатаны. В более старых вариантах выполнения findта же команда будет выполняться так же долго (она должна рекурсивно проверять все файлы на соответствие - в данном случае несуществующим - предикатам), но абсолютно ничего не выведет .)

Прежде чем покинуть тему обработки предикатов, я отмечу, что мое объяснение до сих пор показало, что единственной возможностью для предикатов является И логическое их использование. Это не правда, потому что

  • есть также -oпредикат, который OR два предиката (на самом деле, есть -aпредикат AND, но это редко требуется, потому что, как я писал выше, это поведение по умолчанию);
  • findпозволяет использовать круглые скобки (которые из-за правил экранирования обычно записываются \(и \)) группируют несколько предикатов в одно выражение; а также
  • есть оператор отрицания, который обычно пишется \!.

После всего этого мы можем вернуться к вопросу о том, как получить количество строк для каждого файла с .txtсуффиксом:

  1. Как уже упоминалось, команда для получения количества строк в файле wc -l.
  2. Существует предикат, доступный для запуска команды в проверяемом файле find. Это -exec CMD ;, включая точку с запятой (которая должна быть экранирована при необходимости), и в тексте CMDзаменит любое вхождение токена {}на путь, который в настоящее время проверяется.
  3. Другой предикат позволяет нам проверить суффикс файла: -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будут разбивать список и выполнять команду несколько раз, чтобы каждое имя файла использовалось один раз.

В современных системах это ограничение достаточно велико, так что вам не нужно беспокоиться об этом, пока вы не попадете хотя бы в тысячи имен файлов.

Похожие вопросы