Bash - Periksa direktori untuk file terhadap daftar nama file parsial

8

Saya memiliki server yang menerima file per klien setiap hari ke dalam direktori. Nama file dibuat sebagai berikut:

uuid_datestring_other-data

Sebagai contoh:

d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
  • uuid adalah format standar uuid.
  • datestringadalah keluaran dari date +%Y%m%d.
  • other-data panjang variabel tetapi tidak akan pernah berisi garis bawah.

Saya memiliki file format:

#
d6f60016-0011-49c4-8fca-e2b3496ad5a7    client1
d5873483-5b98-4895-ab09-9891d80a13da    client2
be0ed6a6-e73a-4f33-b755-47226ff22401    another_client
...

Saya perlu memeriksa bahwa setiap uuid yang tercantum dalam file memiliki file yang sesuai di direktori, menggunakan bash.

Saya sudah sejauh ini, tetapi merasa seperti saya datang dari arah yang salah dengan menggunakan pernyataan if, dan saya perlu mengulang-ulang file di direktori sumber.

Variabel source_directory dan uuid_list telah ditetapkan sebelumnya dalam skrip:

# Check the entries in the file list

while read -r uuid name; do
# Ignore comment lines
   [[ $uuid = \#* ]] && continue
   if [[ -f "${source_directory}/${uuid}*" ]]
   then
      echo "File for ${name} has arrived"
   else
      echo "PANIC! - No File for ${name}"
   fi
done < "${uuid_list}"

Bagaimana saya harus memeriksa bahwa file dalam daftar saya ada di direktori? Saya ingin menggunakan fungsionalitas bash sejauh mungkin, tetapi saya tidak menentang penggunaan perintah jika perlu.

Arronikal
sumber
Python? Dan apakah direktori server "datar"?
Jacob Vlijm
Ya itu datar, tidak ada sub-direktori. Saya lebih suka tetap dengan bash jika mungkin.
Arronik
1
Oke, saya tidak akan memposting.
Jacob Vlijm
Saya tidak benar-benar melihat apa yang salah dengan apa yang Anda miliki. Anda harus mengulang melalui UUID atau file, mengapa satu loop lebih baik dari yang lain?
terdon

Jawaban:

5

Berjalan di atas file, buat array asosiatif di atas uuids yang terkandung dalam namanya (saya menggunakan ekspansi parameter untuk mengekstrak uuid). The, baca daftar, periksa array asosiatif untuk setiap uuid dan laporkan apakah file itu direkam atau tidak.

#!/bin/bash
uuid_list=...

declare -A file_for
for file in *_*_* ; do
    uuid=${file%%_*}
    file_for[$uuid]=1
done

while read -r uuid name ; do
    [[ $uuid = \#* ]] && continue
    if [[ ${file_for[$uuid]} ]] ; then
        echo "File for $name has arrived."
    else
        echo "File for $name missing!"
    fi
done < "$uuid_list"
choroba
sumber
1
Bagus (+1), tetapi mengapa ini lebih baik daripada yang dilakukan OP? Anda tampaknya melakukan hal dasar yang sama tetapi dalam dua langkah, bukan satu.
terdon
1
@terdon: Perbedaan utama adalah ini berfungsi :-) Ekspansi wildcard dilakukan sekali saja, tidak setiap kali Anda membaca baris dari daftar, yang mungkin lebih cepat juga.
choroba
Ya, itu perbedaan penting. Cukup adil :)
terdon
Ini terima kasih luar biasa, dapatkan +1 saya. Apakah ada cara untuk memasukkan jalur ke direktori yang menyimpan file? Saya tahu saya bisa cdmasuk ke direktori dalam skrip, tetapi hanya ingin tahu demi mendapatkan pengetahuan.
Arronik
@Arronical: Itu mungkin, tetapi Anda harus menghapus path dari string, mungkin dengan file=${file##*/}.
choroba
5

Berikut ini pendekatan yang lebih "bashy" dan ringkas:

#!/bin/bash

## Read the UUIDs into the array 'uuids'. Using awk
## lets us both skip comments and only keep the UUID
mapfile -t uuids < <(awk '!/^\s*#/{print $1}' uuids.txt)

## Iterate over each UUID
for uuid in ${uuids[@]}; do
        ## Set the special array $_ (the positional parameters: $1, $2 etc)
        ## to the glob matching the UUID. This will be all file/directory
        ## names that start with this UUID.
        set -- "${source_directory}"/"${uuid}"*
        ## If no files matched the glob, no file named $1 will exist
        [[ -e "$1" ]] && echo "YES : $1" || echo  "PANIC $uuid" 
done

Perhatikan bahwa sementara di atas cukup dan akan berfungsi dengan baik untuk beberapa file, kecepatannya tergantung pada jumlah UUID dan akan sangat lambat jika Anda perlu memproses banyak. Jika demikian, gunakan solusi @ choroba atau, untuk sesuatu yang sangat cepat, hindari shell dan hubungi perl:

#!/bin/bash

source_directory="."
perl -lne 'BEGIN{
            opendir(D,"'"$source_directory"'"); 
            foreach(readdir(D)){ /((.+?)_.*)/; $f{$2}=$1; }
           } 
           s/\s.*//; $f{$_} ? print "YES: $f{$_}" : print "PANIC: $_"' uuids.txt

Hanya untuk menggambarkan perbedaan waktu, saya menguji pendekatan bash saya, choroba dan perl saya pada file dengan 20000 UUID yang 18001 memiliki nama file yang sesuai. Perhatikan bahwa setiap pengujian dijalankan dengan mengarahkan output skrip ke /dev/null.

  1. Bash saya (~ 3,5 mnt)

    real   3m39.775s
    user   1m26.083s
    sys    2m13.400s
  2. Choroba's (bash, ~ 0,7 dtk)

    real   0m0.732s
    user   0m0.697s
    sys    0m0.037s
  3. Perl saya (~ 0,1 dtk):

    real   0m0.100s
    user   0m0.093s
    sys    0m0.013s
terdon
sumber
+1 untuk metode ringkas yang fantastis, ini harus dijalankan dari dalam direktori yang berisi file. Saya tahu saya bisa cdmasuk ke direktori dalam skrip, tetapi apakah ada metode di mana jalur file dapat dimasukkan dalam pencarian?
Arronik
@Arronical sure, lihat jawaban yang diperbarui. Anda dapat menggunakan ${source_directory}seperti yang Anda lakukan di skrip.
terdon
Atau gunakan "$2"dan berikan ke skrip sebagai argumen kedua.
alexis
Periksa apakah ini berjalan cukup cepat untuk keperluan Anda - akan lebih cepat melakukannya dengan pemindaian direktori tunggal, daripada banyak pencarian file seperti ini.
alexis
1
@ Alex ya, Anda benar. Saya melakukan beberapa pengujian dan ini menjadi sangat lambat jika jumlah UUID / file meningkat. Saya menambahkan pendekatan perl (yang dapat dijalankan sebagai satu liner dari dalam skrip bash, jadi secara teknis, masih bash jika Anda terbuka untuk penamaan kreatif) yang jauh lebih cepat.
terdon
3

Ini murni Bash (yaitu tidak ada perintah eksternal), dan ini adalah pendekatan yang paling tepat yang dapat saya pikirkan.

Tetapi kinerja-bijaksana benar-benar tidak jauh lebih baik daripada apa yang Anda miliki saat ini.

Ini akan membaca setiap baris dari path/to/file; untuk setiap baris, maka akan menyimpan field pertama di $uuiddan mencetak pesan jika file yang cocok pola path/to/directory/$uuid*ini tidak ditemukan:

#! /bin/bash
[ -z "$2" ] && printf 'Not enough arguments.\n' && exit

while read uuid; do
    [ ! -f "$2/$uuid"* ] && printf '%s missing in %s\n' "$uuid" "$2"
done <"$1"

Sebut saja dengan path/to/script path/to/file path/to/directory.

Output sampel menggunakan file input sampel dalam pertanyaan pada hierarki direktori tes yang berisi file sampel dalam pertanyaan:

% tree
.
├── path
│   └── to
│       ├── directory
│       │   └── d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
│       └── file
└── script.sh

3 directories, 3 files
% ./script.sh path/to/file path/to/directory
d5873483-5b98-4895-ab09-9891d80a13da* missing in path/to/directory
be0ed6a6-e73a-4f33-b755-47226ff22401* missing in path/to/directory
kos
sumber
3
unset IFS
set -f
set +f -- $(<uuid_file)
while  [ "${1+:}" ]
do     : < "$source_directory/$1"*  &&
       printf 'File for %s has arrived.\n' "$2"
       shift 2
done

Idenya di sini adalah tidak perlu khawatir tentang kesalahan pelaporan shell akan melaporkan untuk Anda. Jika Anda mencoba <membuka file yang tidak ada, shell Anda akan mengeluh. Bahkan, itu akan menambahkan skrip Anda$0 dan nomor baris di mana kesalahan terjadi ke output kesalahan ketika itu ... Ini adalah informasi yang baik yang sudah disediakan secara default - jadi jangan repot-repot.

Anda juga tidak perlu mengambil file dalam baris demi baris seperti itu - itu bisa sangat lambat. Ini memperluas seluruh hal dalam satu tembakan ke array argumen dibatasi ruang-putih dan menangani dua sekaligus. Jika data Anda konsisten dengan contoh Anda, maka $1akan selalu menjadi uuid Anda dan $2akan menjadi milik Anda $name. Jika bashdapat membuka kecocokan dengan uuid Anda - dan hanya ada satu kecocokan seperti itu - maka printfterjadilah. Kalau tidak, shell tidak akan menulis diagnosa kepada stderr tentang alasannya.

mikeserv
sumber
1
@kos - apakah file itu ada? jika tidak, maka ia berperilaku sebagaimana dimaksud. unset IFSmemastikan bahwa $(cat <uuid_file)dibagi pada ruang putih. Kerang pecah secara $IFSberbeda ketika hanya terdiri dari ruang putih atau tidak disetel. Ekspansi split seperti itu tidak pernah memiliki bidang nol karena semua urutan ruang putih hanya berdiri sebagai pembatas bidang tunggal. Selama hanya ada dua bidang non-white-space yang terpisah pada setiap baris itu harus bekerja, saya pikir. di bash, anyway. set -fmemastikan bahwa ekspansi tanda kutip tidak diartikan untuk gumpalan, dan set + f memastikan bahwa gumpalan kemudian.
mikeserv
@kos - saya baru saja memperbaikinya. Saya seharusnya tidak menggunakan <>karena itu membuat file tidak ada. <akan melaporkan seperti yang saya maksudkan. masalah yang mungkin dengan itu - dan alasan saya salah digunakan <>di tempat pertama - adalah bahwa jika itu adalah file pipa tanpa pembaca atau seperti dev baris-buffered itu akan hang. yang bisa dihindari dengan menangani output kesalahan lebih eksplisit dan melakukan [ -f "$dir/$1"* ]. kita berbicara tentang uuids di sini, dan karena itu seharusnya tidak pernah berkembang menjadi lebih dari satu file. itu agak bagus meskipun bagaimana melaporkan nama file yang gagal ke stderr seperti itu.
mikeserv
@kos - sebenarnya, saya kira saya bisa menggunakan ulimit agar tidak membuat file sama sekali dan jadi <>masih dapat digunakan dengan cara itu ... <>lebih baik jika gumpal dapat memperluas ke direktori karena pada linux baca / tulis akan gagal dan katakan - itu direktori.
mikeserv
@kos - oh! Maaf - saya hanya bodoh - Anda memiliki dua pertandingan, dan itu melakukan hal yang benar. maksud saya untuk kesalahan dengan cara itu jika dua pertandingan mungkin dimiliki, ini seharusnya uuids - seharusnya tidak pernah ada kemungkinan 2 nama serupa yang cocok dengan bola yang sama. thats sepenuhnya disengaja - dan itu adalah ambigu dengan cara yang seharusnya tidak menjadi. Anda mengerti maksud saya? menamai file untuk gab bukan masalah, - karakter khusus tidak relevan di sini - masalahnya adalah bahwa bashhanya akan menerima gir pengalihan jika hanya cocok dengan satu file. lihat di man bashbawah REDIRECTION.
mikeserv
1

Cara saya mendekatinya adalah dengan mendapatkan uuids dari file terlebih dahulu, lalu gunakan find

awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;done

Untuk keterbacaan,

awk '{print $1}' listfile.txt  | \
    while read fileName;do \
    find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;
    done

Contoh dengan daftar file dalam /etc/, mencari passwd, grup, fstab, dan THISDOESNTEXIST nama file.

$ awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null; done
/etc/pam.d/passwd FOUND
/etc/cron.daily/passwd FOUND
/etc/passwd FOUND
/etc/group FOUND
/etc/iproute2/group FOUND
/etc/fstab FOUND

Karena Anda telah menyebutkan direktori itu datar, Anda bisa menggunakan -printf "%f\n"opsi untuk hanya mencetak nama file itu sendiri

Apa yang tidak dilakukan adalah mendaftar file yang hilang. findKerugian kecilnya adalah ia tidak memberi tahu Anda jika tidak menemukan file, hanya ketika itu cocok dengan sesuatu. Apa yang bisa dilakukan seseorang adalah mengecek output - jika output kosong, maka kita memiliki file yang hilang

awk '{print $1}' listfile.txt  | while read fileName;do RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; [ -z "$RESULT"  ] && echo "$fileName not found" || echo "$fileName found"  ;done

Lebih mudah dibaca:

awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

Dan inilah cara kerjanya sebagai skrip kecil:

skolodya@ubuntu:$ ./listfiles.sh                                               
passwd found
group found
fstab found
THISDONTEXIST not found

skolodya@ubuntu:$ cat listfiles.sh                                             
#!/bin/bash
awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

Satu dapat digunakan statsebagai alternatif, karena itu adalah direktori datar, tetapi kode di bawah ini tidak akan bekerja secara rekursif untuk subdirektori jika Anda memutuskan untuk menambahkannya:

$ awk '{print $1}' listfile.txt  | while read fileName;do  stat /etc/"$fileName"* 1> /dev/null ;done        
stat: cannot stat ‘/etc/THISDONTEXIST*’: No such file or directory

Jika kita mengambil statide dan menjalankannya, kita bisa menggunakan kode keluar stat sebagai indikasi apakah ada file atau tidak. Secara efektif, kami ingin melakukan ini:

$ awk '{print $1}' listfile.txt  | while read fileName;do  if stat /etc/"$fileName"* &> /dev/null;then echo "$fileName found"; else echo "$fileName NOT found"; fi ;done

Contoh dijalankan:

skolodya@ubuntu:$ awk '{print $1}' listfile.txt  | \                                                         
> while read FILE; do                                                                                        
> if stat /etc/"$FILE" &> /dev/null  ;then                                                                   
> echo "$FILE found"                                                                                         
> else echo "$FILE NOT found"                                                                                
> fi                                                                                                         
> done
passwd found
group found
fstab found
THISDONTEXIST NOT found
Sergiy Kolodyazhnyy
sumber