Как получить полные пути рекурсивно в UNIX? - PullRequest
0 голосов
/ 01 ноября 2018

Я ищу способ рекурсивного получения путей ко всем файлам в данном каталоге в UNIX. (без использования find)

Пример:

Учитывая дерево, как это

lab_assignment:
file1.txt
file2.txt
subdir1
subdir2
./subdir1:
file11.txt
./subdir2:
file21.txt

Мне нужна команда, которая бы рекурсивно перечисляла пути ко всем файлам, содержащимся в lab_assignment.

./file1.txt
./file2.txt
./subdir1/file11.txt
./subdir2/file21.txt

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

Учитель сказал нам, что этого можно достичь, используя только ls, цитату и, возможно, трубы и grep.

UPDATE:

Я столкнулся с этой проблемой в недавнем задании, хотя это не было основной целью. Из-за этого мне удалось полностью избежать этой проблемы, но позже мне стало любопытно, каким было правильное решение для нее.

Решение этой проблемы используется в таких задачах, как:
Рекурсивный вывод констант файлов, имена которых заканчиваются на .txt
Рекурсивно считать количество строк во всех файлах, имена которых начинаются с f

Утилиты like cat и wc работают с именами файлов, указанными в их stdin, и не имеют встроенной рекурсивной функциональности, поэтому вы должны предоставить список путей к файлам.

Гадкий путь

Я решил по возможности избежать проблемы и сделал это:

cat *.txt */*.txt */*/*.txt  
wc -l f* */f* */*/f*`  

Это сработало. Учитель выглядел довольно недовольным, называя этот метод грязным и безобразным, но он принял мой доклад. Мне было любопытно, как мне это сделать.

Сломанный путь

После того, как учитель провёл более месяца, он согласился показать мне правильный способ, которым можно было бы сделать это.

Он напечатал это:

cat `ls -R $PWD`

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

Затем он придумал:

cat $PWD/`ls -R`

Эта вещь хоть что-то сделала, но все же - даже близко не к требуемому результату.
Затем учитель сказал мне, что это был его первый год, когда он читал этот курс, который был разработан давно другим отделением универа, и что он, как пользователь UNIX, просто сделает это с find и он не знает решения
но он клянется, что, должно быть, видел это где-то в документации для курса или где-то еще ...

Итак, есть ли способ получить рекурсивный список путей к файлам без поиска? Какой умный кусок UNIX-хитрости и умственной гимнастики является ключом к этому?

Ответы [ 3 ]

0 голосов
/ 02 ноября 2018

TL; DR: Вы можете сделать это, используя только оболочку, без внешних инструментов. Это ниже. Вы также можете сделать это, используя только ls -R плюс некоторую оболочку или используя только инструменты. Смотрите мой другой ответ.

Я искренне заинтересован в том, как сделать это правильно.

«Правильный» путь - find. Это инструмент для этой работы. Это определено в POSIX :

Утилита find должна рекурсивно спускаться по иерархии каталогов из каждого файла, указанного в path, оценивая логическое выражение, составленное из основных цветов, описанных в разделе OPERANDS, для каждого обнаруженного файла.

Я дам вашему инструктору выгоду от сомнения и предположу, что это не какое-то тривиальное академическое упражнение. Я предполагаю, что задание имеет некоторую практичность, например:

"Вы попали в поврежденную систему UNIX, у которой была удалена большая часть набора инструментов, включая команду find. Вам необходимо выполнить сортировку структуры каталогов. Все, что у вас есть, это ls, grep и классическая оболочка Bourne. Вы знаете, что имена файлов являются обычными: в них нет пробелов, в них нет тире, нет управляющих символов и т. д. Как бы вы это сделали? " (1)

(Это пока не получено. Я однажды отключил систему, в которой /usr/bin отсутствовал благодаря ошибочной директиве mount. Мне пришлось диагностировать и восстанавливать ее, используя только встроенные функции оболочки, такие как echo. )

Учитывая это:

$ tree
.
├── file1.txt
├── file2.txt
├── subdir1
│   ├── file11.txt
│   ├── file12.c
│   └── subdira
│       ├── file1a1.c
│       └── file1a1.txt
├── subdir2
│   └── file21.txt

Во-первых, «правильный» путь. Это наш целевой результат:

$ find . -name '*.txt'
./file2.txt
./file1.txt
./subdir1/file11.txt
./subdir1/subdira/file1a1.txt
./subdir2/file21.txt

Итак, есть ли способ получить рекурсивный список путей к файлам без поиска?

Да. Мы можем решить это в этих условиях с помощью только встроенных оболочек:

$ r() {
    d=${1:-.}
    for f in *
    do
        if test -f "$f"; then
            case "$f" in *.txt)
                echo $d/$f
                ;;
            esac
        elif test -d "$f"; then
            ( cd "$f"; r "$d/$f" )
        fi
    done
}
$ r
./file1.txt
./file2.txt
./subdir1/file11.txt
./subdir1/subdira/file1a1.txt
./subdir2/file21.txt

Никаких внешних программ, только встроенные оболочки. Это легко расширяется: вместо повторения совпадения вы можете вызвать программу типа wc. Поскольку это все оболочка, вы можете хранить переменные для суммирования и т. Д.

Но это вряд ли производительно, и его исключают «странные» имена файлов. Кроме того, он не идентичен решению find: find вывод находится в порядке inode, в то время как мое решение оболочки находится в порядке локали. Они могут отличаться, как в моем примере.

Это также не единственный способ сделать рекурсивный спуск, это просто очевидный способ. Альтернативную версию рекурсивного спуска без find см. Rich's POSIX sh tricks .


(1) Если ваш инструктор считает, что это можно правильно сделать с помощью эзотерических имен файлов, содержащих пробелы, управляющие символы, тире и т. Д., Я предлагаю вашему инструктору прочитать трактат Дэвида Уилера (rant) ) по теме.

0 голосов
/ 03 ноября 2018

Если вы ищете чистое инструментальное решение (в отличие от моего другого ответа ), тогда есть несколько вариантов:

tar cvf /dev/null . | grep '\.txt$'
du -a | grep '.txt$' | cut -f2

Если вы ищете гибридное решение, как инструмент, так и оболочку, то:

ls -R . | while read l; do case $l in *:) d=${l%:};; "") d=;; *.txt) echo "$d/$l";; esac; done

Этот последний наиболее близок к параметрам, которые дал ваш инструктор.

0 голосов
/ 02 ноября 2018

——— Использование globstar ———

Мне нужна команда, которая рекурсивно перечисляет пути ко всем файлам [...].
[...]
Команда должна быть максимально простой.

Если у вас bash> 4.0 и в текущем каталоге есть хотя бы один файл, вы можете использовать

shopt -s globstar
printf ./%s\\n **

Когда рабочий каталог может быть пустым, используйте

shopt -s globstar nullglob
a=(**)
(( ${#a[@]} > 0 )) && printf ./%s\\n "${a[@]}"

И для решения явных заданий

Рекурсивно выводить содержимое файлов, имена которых заканчиваются на .txt

shopt -s globstar
cat **/*.txt

Рекурсивно считать количество строк во всех файлах, имена которых начинаются с f

shopt -s globstar
wc -l **/f*

Обратите внимание, что **/* также соответствует файлам в рабочем каталоге. Расширенный список может иметь или не иметь пути с / внутри.


——— Использование ls / grep ———

Учитель сказал нам, что этого можно достичь, используя только ls, цитату и, возможно, трубы и grep

Я так не думаю, по крайней мере ненадежно . Если какое-либо имя файла / каталога содержит разрыв строки, невозможно заставить его работать, используя только упомянутые механизмы.

Если вы можете делать предположения, например », ни один путь не содержит новой строки « или даже », ни один путь не содержит пробелов «, тогда назначение становится разрешимым. Однако я не смог найти решение, которое использует ls, так как ls никогда не выводит полные пути, и нам не хватает инструментов (например, sed, рекурсия или цикл) для построения полных путей из его вывода.

Список путей всех файлов (но не каталогов)

grep -RLE '$^'

-R применяет grep ко всем файлам рекурсивно. -E '$^' это регулярное выражение, которое никогда не совпадает. -L печатает все файлы, которые не совпадают.

Печатать содержимое всех файлов, заканчивающихся на .txt

cat $(grep -RLE '$^' | grep -E '\.txt$')

Количество строк всех файлов, начинающихся с f

wc -l $(grep -RLE '$^' | grep -E '(^|/)f[^/]*$')

——— Заключительные замечания ———

На мой взгляд, это задание плохое, не столько потому, что оно может быть решаемо, а потому, что учит плохим практикам (например, не использует правильные инструменты, полагается на предположения, ...).

...