Bir dizin yapısındaki her dosya hakkında bilgi toplamak için özyinelemeli bash betiği


14

Bir dizin ağacı üzerinden özyinelemeli olarak nasıl çalışabilir ve her dosyada belirli bir komutu yürütebilirim ve yol, dosya adı, uzantı, dosya boyutu ve diğer bazı belirli metinleri bash içindeki tek bir dosyaya çıktılar.


lol, düzenleme için teşekkürler; Ben aşırı karmaşık şeyleri kabul eden ilk kişi olacağım, çünkü hooman dünyasında 800 alakasız soru sormaya alışkınım; bu yüzden sorulardaki bariz olanları cevaplamaya çalışıyorum; ben öğreneceğim olsa :-)
SPooKYiNeSS

1
Tamam, soru ne yapılması gerektiği konusunda oldukça net, sanırım dizin ağacı ve her dosya hakkında bilgi çıktı. Soru oldukça açık ve cevapların miktarına bakıldığında, insanlar bunu oldukça iyi anlıyorlar. Belirsiz olduğu için 3 oy gerçekten bu soruya layık değil
Sergiy Kolodyazhnyy

Yanıtlar:


16

findÇözümler basit ve güçlü olsa da , birkaç gün önce gördüğüm bu ilginç işleve dayanan daha karmaşık bir çözüm oluşturmaya karar verdim .

1. denilen yürütülebilir komut dosyası oluşturun walkbulunan, /usr/local/binkabuk komutu olarak erişilebilir olması için:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • Aşağıdaki kod içeriğini kopyalayın ve şurada kullanın nano: Shift+ Insertfor paste; Ctrl+ Ove Enterkaydetmek için; Ctrl+ Xçıkış için.

2. Komut dosyasının içeriği 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. Açıklama:

  • walk()Fonksiyonun ana mekanizması cevabında Zanna tarafından oldukça iyi tanımlanmıştır . Bu yüzden sadece yeni kısmı anlatacağım.

  • walk()İşlev içinde bu döngüyü ekledim:

    for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done

    Bu $entry, bir dosya olan her biri için işlev yürütüleceği anlamına gelir file_specification().

  • Fonksiyonun file_specification()iki bölümü vardır. İlk bölüm, dosya adı, yol, boyut vb. İle ilgili verileri alır. İkinci bölüm verileri iyi biçimlendirilmiş biçimde çıkarır. Verileri biçimlendirmek için komut kullanılır printf. Senaryoyu değiştirmek istiyorsanız, bu komut hakkında okumalısınız - örneğin bu makale .

  • İşlev file_specification(), her dosya için yürütülmesi gereken belirli komutu koyabileceğiniz iyi bir yerdir . Bu biçimi kullanın:

    "$ {entry}" komutu

    Veya komutun çıktısını değişken olarak kaydedebilir ve daha sonra printfbu değişken, vb .:

    MY_VAR = "$ ( " $ {entry} ") komutu "
    printf "% * s \ tDosya boyutu: \ t $ {YEL}% s $ {NCL} \ n" $ ((girinti + 4)) '' "$ MY_VAR"

    Veya doğrudan printfkomutun çıktısı:

    printf "% * s \ tDosya boyutu: \ t $ {YEL}% s $ {NCL} \ n" $ ((girinti + 4)) '' "$ ( komut " $ {entry} ")"

  • Dilenilen bölüm olarak adlandırılan bölüm , çıktıyı renklendirmek için komut Colourise the outputiçinde kullanılan birkaç değişkeni başlatır printf. Bununla ilgili daha fazla bilgiyi burada bulabilirsiniz .

  • Komut dosyasının altına mutlak ve göreli yollar ile ilgili ek koşul eklenir.

4. Kullanım örnekleri:

  • walkGeçerli dizinde çalıştırmak için:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • walkHerhangi bir alt dizinde çalıştırmak için:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • walkBaşka bir dizinde çalıştırmak için:

    walk /full/path/to/<directory name>
  • Çıktıya dayalı olarak bir metin dosyası oluşturmak için walk:

    walk > output.file
  • Renk kodları olmadan çıktı dosyası oluşturmak için ( kaynak ):

    walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > output.file

5. Kullanım gösterimi:

resim açıklamasını buraya girin


Bu çok fazla iş, ama iyi görünüyor. Aferin !
Sergiy Kolodyazhnyy

@ Pa4080 bu gifleri yapmak için hangi işlemi kullanıyorsunuz?
pbhj

@pbhj, Ubuntu altında Peek kullanıyorum , basit ve güzel, ancak bazen çöküyor ve düzenleme yetenekleri yok. GIF'lerimin çoğu, VNC bağlantısının penceresini kaydettiğim Windows altında oluşturuldu. Ben esas olarak MS Office ve GIF oluşturma için kullandığım ayrı bir masaüstü makine var :) Orada kullandığım araç ScreenToGif olduğunu . Açık kaynak kodlu, ücretsiz ve güçlü bir editör ve işleme mekanizmasına sahiptir. Ne yazık ki Ubuntu için ScreenToGif gibi bir araç bulamıyorum.
pa4080

13

Neden henüz kimsenin yayınlamadığı konusunda biraz şaşkınım, ancak seçeneği bashetkinleştirir globstarve **glob kullanırsanız gerçekten özyinelemeli yeteneklere sahiptir . Bu şekilde, bash bu özyinelemeli globstar'ı kullanan (neredeyse) saf komut dosyası yazabilirsiniz:

#!/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

Burada istediğimiz dosya adının parçalarını almak için parametre genişletme kullandığımıza ve dosya boyutunu almak duve çıktıyı temizlemek dışında harici komutlara güvenmediğimize dikkat edin awk.

Dizin ağacınızı geçtikçe, çıktınız şöyle olmalıdır:

Path: ./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize: 326

Komut dosyası kullanımının standart kuralları geçerlidir: çalıştırılabilir olduğundan emin olun chmod +x ./myscript.shve geçerli dizinden çalıştırın ./myscript.shveya yerleştirin ~/binve çalıştırın source ~/.profile.


Tam dosya adını yazdırıyorsanız, "uzantı" size ne kazandırır? Belki de "$(file "$i")"(yukarıdaki komut dosyasında bir printf'in ikinci kısmı olarak) MIME bilgisini gerçekten istiyorsunuz ?
pbhj

1
Kişisel olarak bana mı? Hiçbir şey değil. Ama soruyu sordu OP istedi output the path, filename, extension, filesize , bu yüzden cevap sorulanla eşleşiyor. :)
Sergiy Kolodyazhnyy

12

findİşi yapmak için kullanabilirsiniz

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

Yalnızca boyutu olan tüm dosyaları listelemek istiyorsanız bu size yardımcı olacaktır.

-exec\;dosyaları tek tek ayrıştırmak için kullanılan her dosya için özel komut veya komut dosyası yürütmenize izin verir , +;bunları birleştirmek istiyorsanız kullanabilirsiniz (dosya adları anlamına gelir).


Bu güzel, ama OP belirtilen tüm gereksinimleri cevap değil.
αғsнιη

1
@ αғsнιη Ona sadece üzerinde çalışacağı bir şablon verdim. Biliyorum, bu soruya tam bir cevap değil, çünkü sorunun kendisinin geniş kapsamda olduğunu düşünüyorum.
Rajesh Rajendran

6

İle findsadece.

find /path/ -type f -printf "path:%h  fileName:%f  size:%kKB Some Text\n" > to_single_file

Veya bunun yerine aşağıda kullanabilirsiniz:

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
Zarif ve basit (eğer biliyorsanız find -printf). +1
David Foerster

1

Ağacın ne kadar derin olduğunu biliyorsanız, en kolay yol joker karakteri kullanmak olacaktır *.

Kabuk betiği veya işlev olarak yapmak istediğiniz her şeyi yazın

function thing() { ... }

sonra koş for i in *; do thing "$i"; done,, for i in */*; do thing "$i"; done... vs

İşleviniz / komut dosyanızda, çalışmak istediğiniz dosyaları belirlemek ve onlarla ne yapmak istiyorsanız onu yapmak için bazı basit testleri kullanabilirsiniz .


"Dosya adlarınızdan herhangi birinde boşluk varsa bu işe yaramaz" ... çünkü değişkenlerinizi alıntılamayı unuttunuz! Bunun yerine "$ i" kullanın $i.
muru

muru no, çalışmaz olmasının nedeni, "for" döngüsü boşluklara ayrılır - " / 'tüm dosyaların boşlukla ayrılmış bir listeye genişletilmesidir. Örneğin, IFS ile uğraşarak, ancak bu noktada sadece find
komutunu da

@ pa4080 bu cevap ile ilgili değil, ama yine de süper yararlı görünüyor, teşekkürler!
Benubird

Bence nasıl for i in */*çalıştığını anlamıyorsun . İşte, test edin:for i in */*; do printf "|%s|\n" "$i"; done
muru

İşte tırnak işaretleri öneminin bir kanıtı: i.stack.imgur.com/oYSj2.png
pa4080

1

find bunu yapabilirsin:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'

man findDiğer dosya özelliklerine göz atın .

Uzantıya gerçekten ihtiyacınız varsa, bunu ekleyebilirsiniz:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.