Как рекурсивно вывести содержимое файлов с номерами строк из директории abc имя которых заканчивается на t, строки отсортировать. find не использовать
Прбовал это сделать через ls -R | grep -n "t$" | sort, но тогда он просто сортирует название файлов, а как получить содержимое файлов я не знаю. Интернет мне не помог :(
Ответы (1 шт):
Рекурсивный обход средствами bash
Возможно, это задание на программирование в bash - рекурсивные функции без использования специализированных утилит, которые умеют обходить дерево каталогов.
Скрипт из исходного ответа работает почти правильно, но спотыкается на именах файлов, которые содержат символ новой строки \n - в юниксовых файловых системах это допустимый символ. Вообще в именах файлов допустимы любые байтовые последовательности за исключением \x00 и /. Поэтому скрипт, который читает имя из stdin функцией read рискует сломаться на многострочных именах, или именах с управляющими символами.
Зато встроенный в bash механизм gobbling совершенно свободно манипулирует любыми байтами:
for f in *; do
# в `$f` могут быть имена с любыми байтами внутри
done
Поэтому средствами bash можно обрабатывать любые имена файлов и каталогов. В скрипте myfind.sh функция visit ARG:
- если ARG является файлом, вызывает обработчик файлов
- если ARG является каталогом, то вызывает
visitдля всех дочерних каталогов и файловARG.
#!/bin/bash
# myfind.sh [DIR]
# Если параметр DIR не задан, то эквивалентно myfind.sh .
# Вывод отладочной информации в /dev/stderr
function log {
echo "DEBUG: $*" >> /dev/stderr
}
# file_action FILENAME
# Выполняет действие для файла FILENAME
function file_action {
if [ -n "$1" ] && [ -f "$1" ] && [ "t" == "${1: -1}" ]; then
cat -n "$1"
fi
}
# `visit file_or_dir` обрабатывает файл или каталог с именем `file_or_dir`.
#
# Если это каталог, то рекурсивно обрабатывает все его содержимое.
# Если это файл, то вызывает для него file_action
function visit {
# если аргумент не задан, то ничего не делать
test -z "$1" && return
f="${1}"
log "visit: $f"
# если аргумент является каталогом
if [ -d "$f" ]; then
# посетить всех детей, за исключением . и ..
for kid in "$f"/* "$f"/.[^.]*; do
visit "$kid"
done
return
fi
# если аргумент является файлом
if [ -f "$f" ]; then
log "-> file: $f"
file_action "$f"
fi
}
# начинаем обход с каталога, заданного первым аргументом,
# или с текущего каталога, если скрипт вызывается без аргументов
visit "${1:-.}"
Понятное дело, этот скрипт не production grade, он лишь иллюстрирует идею рекурсивного обхода на bash. В частности, он не умеет распознавать циклы (в Linux можно создавать hard links на каталоги, что может привести к циклам в именах файлов), игнорирует символические ссылки, ограничен глубиной рекурсии в bash.
Есть генератор тестов mktest.sh. Он создаёт дерево каталогов и файлов с именами, содержащими пробел, табуляцию, символ новой строки и перевода каретки, символ backspace, скрытый каталог .dot, каталоги с именем,завершающимся на t, файлы .txt (должны обрабатываться) и .md (должны игнорироваться).
d=./abc
rm -rf "$d"
for slot in a 'b c' "$(echo -e 'd\n e')" .dot "$(echo -e 'f\tg')" "$(echo -e 'h\ri')" "$(echo -e 'j\bk')" t tt ttt; do
mkdir -p "$d"
f="$d/$slot.txt";
d="$d/$slot"
echo "$f" >> /dev/stderr
for i in `seq 1 $(( RANDOM % 20 ))`; do
echo "Line $i of $f"
done > "$f"
f="${f}.md"
for i in `seq 1 $(( RANDOM % 20 ))`; do
echo "Line $i of $f"
done > "$f"
done
Как запустить пример:
bash mktest.shсоздаст каталогabcc тестовыми примерами.bash myfind.sh abcобойдёт рекурсивно каталогabcи обработает все файлы.
Изначальный ответ
tree -fFai | grep -E "t$" | while read f; do test -f "$f" && cat -n "$f"; done | sort
если tree нет, см. UPDATE ниже
Как работает этот скрипт:
tree -fFai | grep -E "t$"- рекурсивно находит все файлы, чьи имена завершаются наt.-fпечатает полные имена,-Fпечатает/на конце пути для каталогов (и ещё несколько спецсимволов для нерегулярных файлов)-aдобавлен, чтобы в вывод попали все файлы-iпечатает сплошной список файлов и каталогов без отступов
while read f; do ... done- в цикле имена файлов считываются в переменнуюf.test -f "$f" && cat -n "$f"проверяет, что путь$fуказывает на файл, и если это так, выводит содержимое файла$fс номерами строк. Переменная взята в кавычки на случай пробелов в имени файла.| sortсортирует все строки из всех файлов.
Пример
Создание набора файлов
d=.
for l in a b c d e f g h; do
mkdir -p $d
f=$d/$l.txt;
d=$d/$l
echo $f >> /dev/stderr
for i in `seq 1 ${RANDOM}`; do
echo "Line $i of $f"
done > $f
done
Создаются дерево файлов
./
./a/
./a/b/
./a/b/c/
./a/b/c/d/
./a/b/c/d/e/
./a/b/c/d/e/f/
./a/b/c/d/e/f/g/
./a/b/c/d/e/f/g/h.txt
./a/b/c/d/e/f/g.txt
./a/b/c/d/e/f.txt
./a/b/c/d/e.txt
./a/b/c/d.txt
./a/b/c.txt
./a/b.txt
./a.txt
Вывод
1 Line 1 of ./a.txt
1 Line 1 of ./a/b.txt
1 Line 1 of ./a/b/c.txt
1 Line 1 of ./a/b/c/d.txt
1 Line 1 of ./a/b/c/d/e.txt
1 Line 1 of ./a/b/c/d/e/f.txt
1 Line 1 of ./a/b/c/d/e/f/g.txt
1 Line 1 of ./a/b/c/d/e/f/g/h.txt
2 Line 2 of ./a.txt
2 Line 2 of ./a/b.txt
2 Line 2 of ./a/b/c.txt
2 Line 2 of ./a/b/c/d.txt
2 Line 2 of ./a/b/c/d/e.txt
2 Line 2 of ./a/b/c/d/e/f.txt
2 Line 2 of ./a/b/c/d/e/f/g.txt
2 Line 2 of ./a/b/c/d/e/f/g/h.txt
3 Line 3 of ./a.txt
3 Line 3 of ./a/b.txt
3 Line 3 of ./a/b/c.txt
3 Line 3 of ./a/b/c/d.txt
3 Line 3 of ./a/b/c/d/e.txt
3 Line 3 of ./a/b/c/d/e/f.txt
3 Line 3 of ./a/b/c/d/e/f/g.txt
3 Line 3 of ./a/b/c/d/e/f/g/h.txt
UPDATE
Если в вашей системе нет tree, то можно обойти подкаталоги и другими средствами. Например, утилита du, которая измеряет размер файлов и каталогов, встречается везде, даже в микроскопическом alpine:
du -m -a | cut -f 2 | grep -E 't$' | while read f; do test -f "$f" && cat -n "$f"; done | sort
du -m -a печатает размеры файлов и каталогов в мегабайтах, формат sizeTABpath. Команда cut -f 2 печатает второй столбец, по-умолчанию между столбцами разделитель символ табуляции. Как раз то, что нужно для разбора вывода du. Правда, этот метод сломается на файлах, содержащих TAB внутри имени.
Проверено в Ubuntu 22 и alpine 3.21