Как мне рекурсивно работать через дерево каталогов и выполнять определенную команду для каждого файла, и выводить путь, имя файла, расширение, размер файла и некоторый другой конкретный текст в один файл в bash.
Как мне рекурсивно работать через дерево каталогов и выполнять определенную команду для каждого файла, и выводить путь, имя файла, расширение, размер файла и некоторый другой конкретный текст в один файл в bash.
Ответы:
Хотя findрешения просты и эффективны, я решил создать более сложное решение, основанное на этой интересной функции , которую я видел несколько дней назад.
1. Создайте исполняемый файл скрипта, который называется walk, и который /usr/local/binбудет доступен для команды оболочки:
sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
nano: Shift+ Insertдля вставки; Ctrl+ Oи Enterдля сохранения; Ctrl+ Xза выход.2. Содержание скрипта walk:
#!/bin/bash
# Colourise the output
RED='\033[0;31m' # Red
GRE='\033[0;32m' # Green
YEL='\033[1;33m' # Yellow
NCL='\033[0m' # No Color
file_specification() {
FILE_NAME="$(basename "${entry}")"
DIR="$(dirname "${entry}")"
NAME="${FILE_NAME%.*}"
EXT="${FILE_NAME##*.}"
SIZE="$(du -sh "${entry}" | cut -f1)"
printf "%*s${GRE}%s${NCL}\n" $((indent+4)) '' "${entry}"
printf "%*s\tFile name:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$FILE_NAME"
printf "%*s\tDirectory:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$DIR"
printf "%*s\tName only:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$NAME"
printf "%*s\tExtension:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$EXT"
printf "%*s\tFile size:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$SIZE"
}
walk() {
local indent="${2:-0}"
printf "\n%*s${RED}%s${NCL}\n\n" "$indent" '' "$1"
# If the entry is a file do some operations
for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
# If the entry is a directory call walk() == create recursion
for entry in "$1"/*; do [[ -d "$entry" ]] && walk "$entry" $((indent+4)); done
}
# If the path is empty use the current, otherwise convert relative to absolute; Exec walk()
[[ -z "${1}" ]] && ABS_PATH="${PWD}" || cd "${1}" && ABS_PATH="${PWD}"
walk "${ABS_PATH}"
echo
3. Объяснение:
Основной механизм walk()функции довольно хорошо описан Занной в ее ответе . Поэтому я опишу только новую часть.
В walk()функции я добавил этот цикл:
for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
Это означает, что для каждого $entryфайла будет выполнена функция file_specification().
Функция file_specification()состоит из двух частей. Первая часть получает данные, связанные с файлом - имя, путь, размер и т. Д. Вторая часть выводит данные в хорошо отформатированном виде. Для форматирования данных используется команда printf. И если вы хотите настроить скрипт, вы должны прочитать об этой команде - например, эту статью .
Функция file_specification()является хорошим местом, где вы можете поместить конкретную команду, которая должна быть выполнена для каждого файла . Используйте этот формат:
команда "$ {entry}"
Или вы можете сохранить вывод команды как переменную, а затем printfэту переменную и т.д .:
MY_VAR = "$ ( команда " $ {entry} ")"
printf "% * s \ tFile size: \ t $ {YEL}% s $ {NCL} \ n" $ ((отступ + 4)) '' "$ MY_VAR"
Или непосредственно printfвывод команды:
printf "% * s \ tFile size: \ t $ {YEL}% s $ {NCL} \ n" $ ((отступ + 4)) '' "$ ( команда " $ {entry} ")" Раздел с самого начала, называется Colourise the output, инициализирует несколько переменных, которые используются в printfкоманде для окрашивания вывода. Подробнее об этом вы можете найти здесь .
В конец скрипта добавлено дополнительное условие, которое касается абсолютных и относительных путей.
4. Примеры использования:
Чтобы запустить walkдля текущего каталога:
walk # You shouldn't use any argument,
walk ./ # but you can use also this formatЧтобы запустить walkлюбой дочерний каталог:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>Чтобы запустить walkдля любого другого каталога:
walk /full/path/to/<directory name>Чтобы создать текстовый файл на основе walkвывода:
walk > output.fileЧтобы создать выходной файл без цветовых кодов ( источник ):
walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > output.file5. Демонстрация использования:
Я немного озадачен тем, почему никто еще не опубликовал его, но действительно bashимеет рекурсивные возможности, если вы включите globstarопцию и используете **glob. Таким образом, вы можете написать (почти) чистый bash скрипт, который использует этот рекурсивный globstar, например так:
#!/usr/bin/env bash
shopt -s globstar
for i in ./**/*
do
if [ -f "$i" ];
then
printf "Path: %s\n" "${i%/*}" # shortest suffix removal
printf "Filename: %s\n" "${i##*/}" # longest prefix removal
printf "Extension: %s\n" "${i##*.}"
printf "Filesize: %s\n" "$(du -b "$i" | awk '{print $1}')"
# some other command can go here
printf "\n\n"
fi
done
Обратите внимание, что здесь мы используем расширение параметров, чтобы получить части имени файла, которые нам нужны, и мы не полагаемся на внешние команды, за исключением получения размера файла duи очистки вывода с помощью awk.
И поскольку он пересекает ваше дерево каталогов, ваш вывод должен выглядеть примерно так:
Path: ./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize: 326
Применяются стандартные правила использования скрипта: убедитесь, что он исполняемый с chmod +x ./myscript.shи запускайте его из текущего каталога через ./myscript.shили поместите ~/binи запустите source ~/.profile.
"$(file "$i")"(в приведенном выше сценарии как вторая часть printf) будет возвращаться?
output the path, filename, extension, filesize , поэтому ответ соответствует тому, что спрашивают. :)
Ты можешь использовать find чтобы сделать работу
find /path/ -type f -exec ls -alh {} \;
Это поможет вам, если вы просто хотите перечислить все файлы с размером.
-execпозволит вам выполнять пользовательские команды или сценарии для каждого файла,
\;используемого для анализа файлов один за другим, вы можете использовать, +;если хотите объединить их (имеется в виду имена файлов).
С findтолько.
find /path/ -type f -printf "path:%h fileName:%f size:%kKB Some Text\n" > to_single_file
Или вы можете использовать ниже:
find -type f -not -name "to_single_file" -execdir sh -c '
printf "%s %s %s %s Some Text\n" "$PWD" "${1#./}" "${1##*.}" $(stat -c %s "$1")
' _ {} \; > to_single_file
find -printf). +1
Если вы знаете, как глубоко дерево, самый простой способ будет использовать подстановочный знак * .
Запишите все, что вы хотите сделать, в виде сценария оболочки или функции
function thing() { ... }
затем запустить for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done... и т.д.
Внутри вашей функции / скрипта вы можете использовать несколько простых тестов, чтобы выделить файлы, с которыми вы хотите работать, и делать с ними все, что вам нужно.
$i.
for i in */*работает. Вот, проверьте это:for i in */*; do printf "|%s|\n" "$i"; done
find можно сделать это:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'
Посмотрите на man findдругие свойства файла.
Если вам действительно нужно расширение, вы можете добавить это:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;