Рекурсивный скрипт bash для сбора информации о каждом файле в структуре каталогов

14

Как мне рекурсивно работать через дерево каталогов и выполнять определенную команду для каждого файла, и выводить путь, имя файла, расширение, размер файла и некоторый другой конкретный текст в один файл в bash.

SPooKYiNeSS
источник
лол, спасибо за редактирование; я буду первым, кто признает, что я слишком усложняю вещи, потому что я привык задавать 800 не относящихся к делу вопросов в мире людей; поэтому я стараюсь отвечать на очевидные вопросы; Я учусь, хотя :-)
SPooKYiNeSS
1
Хорошо, я думаю, вопрос довольно ясен о том, что должно быть сделано, просмотрите дерево каталогов и выведите информацию о каждом файле. Вопрос достаточно ясен, и, судя по количеству ответов, люди это понимают достаточно хорошо. 3 голоса за непонятность действительно не заслуживают этого вопроса
Сергей Колодяжный

Ответы:

16

Хотя 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. Демонстрация использования:

введите описание изображения здесь

pa4080
источник
Это большая работа, но выглядит хорошо. Молодец !
Сергей Колодяжный,
Какой процесс вы используете, чтобы сделать эти картинки @ pa4080?
pbhj
@pbhj, под Ubuntu я использую Peek, это просто и приятно, но иногда вылетает и не имеет возможностей редактирования. Большинство моих GIF-файлов создаются под Windows, где я записываю окно подключения VNC. У меня есть отдельная настольная машина, которую я в основном использую для создания MS Office и GIF :) Используемый там инструмент - ScreenToGif . Это с открытым исходным кодом, бесплатно, и имеет мощный редактор и механизм обработки. К сожалению, я не могу найти такой инструмент, как ScreenToGif для Ubuntu.
pa4080
13

Я немного озадачен тем, почему никто еще не опубликовал его, но действительно 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.

Сергей Колодяжный
источник
Если вы печатаете полное имя файла, что дает вам «расширение»? Возможно, вы действительно хотите получить информацию MIME, которая "$(file "$i")"(в приведенном выше сценарии как вторая часть printf) будет возвращаться?
pbhj
1
@pbhj лично для меня? Ничего. Но ОП, который задал вопрос, спросил output the path, filename, extension, filesize , поэтому ответ соответствует тому, что спрашивают. :)
Сергей Колодяжный
12

Ты можешь использовать find чтобы сделать работу

find /path/ -type f -exec ls -alh {} \;

Это поможет вам, если вы просто хотите перечислить все файлы с размером.

-execпозволит вам выполнять пользовательские команды или сценарии для каждого файла, \;используемого для анализа файлов один за другим, вы можете использовать, +;если хотите объединить их (имеется в виду имена файлов).

Раджеш Раджендран
источник
Это хорошо, но не отвечает всем упомянутым требованиям ОП.
αғsнιη
1
@ αғsнιη Я просто дал ему шаблон для работы. Я знаю, что это не полный ответ на этот вопрос, так как я думаю, что сам вопрос имеет широкий охват.
Раджеш Раджендран
6

С 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
αғsнιη
источник
2
Элегантно и просто (если вы знаете о find -printf). +1
Дэвид Фёрстер
1

Если вы знаете, как глубоко дерево, самый простой способ будет использовать подстановочный знак * .

Запишите все, что вы хотите сделать, в виде сценария оболочки или функции

function thing() { ... }

затем запустить for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done... и т.д.

Внутри вашей функции / скрипта вы можете использовать несколько простых тестов, чтобы выделить файлы, с которыми вы хотите работать, и делать с ними все, что вам нужно.

Benubird
источник
«это не сработает, если в любом из ваших имен файлов есть пробелы» ... потому что вы забыли процитировать ваши переменные! Используйте «$ i» вместо $i.
Муру
@muru нет, причина, по которой он не работает, заключается в том, что цикл «for» разделяется на пробелы - » / 'превращается в разделенный пробелами список всех файлов. Вы можете обойти это, например, возиться с IFS, но в этот момент вы могли бы просто использовать find
Benubird
@ pa4080 не имеет отношения к этому ответу, но в любом случае это выглядит очень полезным, спасибо!
Benubird
Я думаю, вы не понимаете, как for i in */*работает. Вот, проверьте это:for i in */*; do printf "|%s|\n" "$i"; done
Muru
Вот свидетельство важности кавычек: i.stack.imgur.com/oYSj2.png
pa4080
1

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"' {} \;
Кату
источник