Как рекурсивно вывести содержимое файлов с номерами строк из директории abc имя которых заканчивается на t, строки отсортировать. find не использовать

Прбовал это сделать через ls -R | grep -n "t$" | sort, но тогда он просто сортирует название файлов, а как получить содержимое файлов я не знаю. Интернет мне не помог :(


Ответы (1 шт):

Автор решения: Pak Uula

Рекурсивный обход средствами 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 создаст каталог abc c тестовыми примерами.
  • bash myfind.sh abc обойдёт рекурсивно каталог abc и обработает все файлы.

Изначальный ответ

tree -fFai | grep -E "t$" | while read f; do test -f "$f" && cat -n "$f"; done | sort

если tree нет, см. UPDATE ниже

Как работает этот скрипт:

  1. tree -fFai | grep -E "t$" - рекурсивно находит все файлы, чьи имена завершаются на t.
    • -f печатает полные имена,
    • -F печатает / на конце пути для каталогов (и ещё несколько спецсимволов для нерегулярных файлов)
    • -a добавлен, чтобы в вывод попали все файлы
    • -i печатает сплошной список файлов и каталогов без отступов
  2. while read f; do ... done - в цикле имена файлов считываются в переменную f.
  3. test -f "$f" && cat -n "$f" проверяет, что путь $f указывает на файл, и если это так, выводит содержимое файла $f с номерами строк. Переменная взята в кавычки на случай пробелов в имени файла.
  4. | 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

→ Ссылка