2

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

for i in ls B 
do
  echo $i
done

2 ответа2

2

На странице 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
0

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

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

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

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

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

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

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

find DIR [PREDICATE, ..]

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

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

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

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

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

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

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

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

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

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

  • также существует предикат -o который определяет два предиката OR (на самом деле, существует предикат -a И, но это редко требуется, потому что, как я писал выше, это поведение по умолчанию);
  • 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 разбивают список и запускают команду несколько раз, чтобы каждое имя файла использовалось один раз.

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .