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


14

Як я працюю рекурсивно через дерево каталогів і виконую певну команду для кожного файлу, і виводите шлях, ім'я файлу, розширення, розмір файлів та якийсь інший конкретний текст до одного файлу в bash.


хаха, дякую за редагування; я першим визнаю, що я надмірно ускладнюю речі, бо мені звикли задавати 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 \ t Розмір файлу: \ t $ {YEL}% s $ {NCL} \ n" $ ((відступ + 4)) '' "$ MY_VAR"

    Або безпосередньо printfвихід команди:

    printf "% * s \ t Розмір файлу: \ t $ {YEL}% s $ {NCL} \ n" $ ((відступ + 4)) '' "$ ( команда " $ {запис} ")"

  • Розділ випробовування, який називається 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. Демонстрація використання:

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


Це багато роботи, але виглядає добре. Хороша робота !
Сергій Колодяжний

Який процес ви використовуєте для створення цих gif @ pa4080?
pbhj

@pbhj, в Ubuntu я використовую Peek - це просто і приємно, але іноді виходить з ладу і не має можливостей редагування. Більшість моїх GIF створені під Windows, де я записую вікно підключення VNC. У мене є окрема настільна машина, яку в основному я використовую для створення MS Office та GIF :) Інструмент, який я там використовую, - ScreenToGif . Він відкритий, безкоштовний і має потужний редактор та механізм обробки. На жаль, я не можу знайти такий інструмент, як ScreenToGif для Ubuntu.
pa4080

13

Я трохи здивований, чому ще ніхто не опублікував це, але дійсно bashмає рекурсивні можливості, якщо ви включите globstarопцію та використовуєте **глобус. Таким чином, ви можете написати (майже) чистий bash скрипт, який використовує такий рекурсивний глобуляр на зразок цього:

#!/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дозволить вам виконати власну команду або скрипт для кожного файлу, який \;використовується для розбору файлів один за одним, ви можете використовувати їх, +;якщо ви хочете об'єднати їх (означає імена файлів).


Це приємно, але не відповідає всім згаданим вимогам ОП.
αғsnιη

1
@ αғsnιη Я щойно дав йому шаблон для роботи. Я знаю, це не є повною відповіддю на це запитання, оскільки я вважаю, що саме питання має широкий обсяг.
Rajesh Rajendran

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

2
Елегантний і простий (якщо ви знаєте про це find -printf). +1
Девід Фоерстер

1

Якщо ви знаєте, наскільки глибоко дерево, найпростішим способом буде використання магістра *.

Запишіть усе, що ви хочете зробити, як сценарій оболонки або функцію

function thing() { ... }

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

У межах своєї функції / сценарію ви можете використовувати кілька простих тестів, щоб виділити файли, з якими ви хочете працювати, і робити з ними все, що вам потрібно.


"це не спрацює, якщо будь-яка з ваших імен файлів має пробіли" ... тому що ви забули процитувати ваші змінні! Використовуйте "$ i" замість $i.
муру

@muru ні, причина цього не працює в тому, що цикл "for" розпадається на пробіли - " / " розширюється в список розділених пробілом всіх файлів. Ви можете обійтися цим, наприклад, повозившись з IFS, але в цей момент ви можете просто скористатися знахідкою
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"' {} \;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.