Как найти файлы в подкаталогах и отсортировать их по имени в одной команде?

9

Результат обычной находки с использованием find . ! -path "./build*" -name "*.txt":

./tool/001-sub.txt
./tool/000-main.txt
./zo/001-int.txt
./zo/id/002-and.txt
./as/002-mod.txt

и когда сортируется с sort -n:

./as/002-mod.txt
./tool/000-main.txt
./tool/001-sub.txt
./zo/001-int.txt
./zo/id/002-and.txt

Однако желаемый результат:

./tool/000-main.txt
./zo/001-int.txt
./tool/001-sub.txt
./zo/id/002-and.txt
./as/002-mod.txt

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

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

unode
источник
2
Посмотрите этот вопрос, который я задал на SO: stackoverflow.com/questions/3222810/…
camh
@camh - если возможно, я бы хотел использовать только команды Unix. В любом случае мой вопрос в значительной степени дублирует ваш. Можете ли вы перенести лучшее решение в эту ветку (в любом случае сохранить ссылку на оригинал), чтобы я мог отметить это как решение?
unode
Если @Shawn внесет изменения, которые я предложил в своем комментарии (используйте -printfвместо awk), я думаю, что это лучшее решение. Я переработал мою первоначальную реализацию, чтобы использовать этот метод.
Camh

Ответы:

9

Вам нужно отсортировать по последнему полю (рассматривая /как разделитель полей). К сожалению, я не могу вспомнить инструмент, который может сделать это, когда количество полей меняется (если бы только sort -kмогло принимать отрицательные значения).

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

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

Эта awkкоманда говорит, что разделитель полей FS установлен в /; это влияет на способ чтения полей. Сепаратор поля вывода OFS также имеет значение /; это влияет на способ печати записей. Следующий оператор говорит: напечатать последний столбец ( NFэто число полей в записи, поэтому он также является индексом последнего поля), а также всю запись ( $0это вся запись); он напечатает их с OFS между ними. Затем список sortредактируется, рассматривая его /как разделитель полей - так как у нас есть имя файла первым в записи, оно будет отсортировано по этому. Затем cutпечатает только поля 2 до конца, снова обрабатывая /как разделитель полей.

Шон Дж. Гофф
источник
3
Так как это с find (1), вы можете пропустить часть awk и использовать-printf '%f/%p\n'
camh
действительно, наша установка немного сложнее. Он включает в себя переменные глубины subdir. Отредактировал вопрос, чтобы отразить этот факт. Мои извинения за то, что я не включил это сначала.
unode
1
@Unode: решение Шона прекрасно обрабатывает переменную глубину, это каноническое решение этой проблемы (с небольшими изменениями).
Жиль "ТАК - перестань быть злым"
4

Я бы использовал файлы '-printf' для вывода имени и пути, сортировки по имени и обрезания имени на последнем шаге. «###» - это просто маркер, помогающий резать.

find -name "*.txt" -printf "%f###%p\n" | sort -n | sed 's/.*###//'

% f печатает имя файла,% p весь путь.

Я упростил команду find, чтобы вывести ее в одну строку, конечно, вы бы оставили ! -path "./build*"часть.

Пользователь неизвестен
источник
3

В зш ≥4.3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txtсовпадения *.txtв текущем каталоге и его подкаталогах рекурсивно .
  • ~build* исключает совпадения, текст которых начинается с build*(как ! -path './build*'). ( setopt extended_globСначала нужно .)
  • (oe\''…'\')является классификатором глобуса сортировки . REPLY=…создает строку для сортировки из строки для возврата.
  • ${REPLY:t}это базовое имя («хвост») пути.
Жиль "ТАК - перестань быть злым"
источник
Много каскадной магии. Интересно, но мы ограничены синтаксисом sh. +1
unode