Как мне рекурсивно работать через дерево каталогов и выполнять определенную команду для каждого файла, и выводить путь, имя файла, расширение, размер файла и некоторый другой конкретный текст в один файл в 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.file
5. Демонстрация использования:
Я немного озадачен тем, почему никто еще не опубликовал его, но действительно 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"' {} \;