বাশ - আংশিক ফাইল নামের তালিকার বিপরীতে ফাইলগুলির জন্য ডিরেক্টরি পরীক্ষা করুন


8

আমার একটি সার্ভার রয়েছে যা প্রতি ক্লায়েন্টের জন্য একটি ফাইল একটি ডিরেক্টরিতে নিয়ে যায়। ফাইলের নামগুলি নিম্নরূপভাবে নির্মিত হয়েছে:

uuid_datestring_other-data

উদাহরণ স্বরূপ:

d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
  • uuid uuid একটি মানক বিন্যাস।
  • datestringআউটপুট থেকে date +%Y%m%d
  • other-data দৈর্ঘ্যে পরিবর্তনশীল তবে কোনও আন্ডারস্কোর থাকতে পারে না।

আমার কাছে ফর্ম্যাটটির একটি ফাইল রয়েছে:

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

আমাকে পরীক্ষা করে দেখতে হবে যে ফাইলটিতে তালিকাভুক্ত প্রত্যেক ইউইডির বাশ ব্যবহার করে ডিরেক্টরিতে একটি ফাইল রয়েছে।

আমি এ পর্যন্ত পেয়েছি তবে মনে হচ্ছে আমি যদি একটি if স্টেটমেন্ট ব্যবহার করে ভুল দিক থেকে আসছি এবং উত্স ডিরেক্টরিতে থাকা ফাইলগুলি আমার লুপ করতে হবে।

সোর্স_ডাইরেক্টরি এবং ইউইউডি_লিস্ট ভেরিয়েবলগুলি স্ক্রিপ্টে আগে বরাদ্দ করা হয়েছিল:

# 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}"

আমার তালিকার ফাইলগুলি ডিরেক্টরিতে উপস্থিত আছে কিনা তা আমার কীভাবে পরীক্ষা করা উচিত? আমি যতদূর সম্ভব ব্যাশ কার্যকারিতাটি ব্যবহার করতে চাই তবে প্রয়োজনে কমান্ডগুলি ব্যবহারের বিরুদ্ধে নয়।


পাইথন? এবং সার্ভার ডিরেক্টরি "ফ্ল্যাট"?
জ্যাকব Vlijm

হ্যাঁ এটি ফ্ল্যাট, কোনও উপ-ডিরেক্টরি নয়। আমি যদি সম্ভব হয় তবে কেবল বাশ দিয়ে থাকি।
অ্যারোনিকাল

1
ঠিক আছে, আমি পোস্ট করব না।
জ্যাকব Vlijm


আপনার কাছে যা আছে তাতে আমি আসলেই কী দেখতে পাচ্ছি না। আপনাকে ইউআইডি বা ফাইলগুলির মধ্য দিয়ে লুপ করতে হবে, কেন একটি লুপ অন্যটির চেয়ে ভাল হবে?
টেরডন

উত্তর:


5

ফাইলগুলির ওপরে যান, তাদের নামে থাকা ইউইডগুলির উপরে একটি সহযোগী অ্যারে তৈরি করুন (আমি ইউইউইডটি বের করার জন্য প্যারামিটার সম্প্রসারণ ব্যবহার করেছি)। , তালিকাটি পড়ুন, প্রতিটি ইউইডির জন্য সহযোগী অ্যারে পরীক্ষা করুন এবং ফাইলটি রেকর্ড করা হয়েছে কিনা তা রিপোর্ট করুন।

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

1
ভাল (+1), তবে ওপি যা করছিল তার থেকে এটি কেন ভাল? আপনি একই বুনিয়াদি কাজ করছেন বলে মনে হচ্ছে তবে একটির পরিবর্তে দুটি পদক্ষেপে।
টেরডন

1
@ ইটারডন: মূল পার্থক্যটি এটির কাজ করে :-) ওয়াইল্ডকার্ড সম্প্রসারণ কেবল একবার করা হয়, প্রতিবার আপনি তালিকা থেকে কোনও লাইন পড়েন না, এটিও দ্রুত হতে পারে।
চোরোবা

হ্যাঁ, এটি একটি গুরুত্বপূর্ণ পার্থক্য। মোটামুটি যথেষ্ট :)
টেরডন

এটি দুর্দান্ত + আপনাকে ধন্যবাদ, আমার +1 পেয়েছে। যে ডিরেক্টরিটিতে ফাইল রয়েছে তাতে পাথ যুক্ত করার কোনও উপায় আছে কি? আমি জানি আমি cdস্ক্রিপ্টের মধ্যে ডিরেক্টরিতে প্রবেশ করতে পারি , তবে জ্ঞান অর্জনের স্বার্থে অবাক হয়েছি ।
অ্যারোনিকাল

@ অ্যারোনিকাল: এটি সম্ভব, তবে আপনাকে স্ট্রিং থেকে সম্ভাব্য পথটি সরিয়ে ফেলতে হবে file=${file##*/}
চোরোবা

5

এখানে আরও একটি "বাশী" এবং সংক্ষিপ্ত পদ্ধতির:

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

মনে রাখবেন যে উপরেরটি বেশ সুন্দর এবং কয়েকটি ফাইলের জন্য সূক্ষ্মভাবে কাজ করবে, এর গতি ইউআইডির সংখ্যার উপর নির্ভর করে এবং আপনাকে অনেকগুলি প্রক্রিয়া করার প্রয়োজন হলে খুব ধীর হবে । যদি এটি হয় তবে @ চোরোবার সমাধানটি ব্যবহার করুন বা সত্যিকারের কোনও কিছুর জন্য শেলটি এড়িয়ে কল করুন 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

কেবল সময়ের পার্থক্য বোঝানোর জন্য, আমি 20000 ইউআইডি সহ একটি ফাইলটিতে আমার বাশ পদ্ধতির, চোরোবা এবং আমার পার্লটি পরীক্ষা করেছিলাম যার মধ্যে 18001 এর সাথে সম্পর্কিত ফাইলের নাম ছিল। নোট করুন যে প্রতিটি পরীক্ষা স্ক্রিপ্টের আউটপুটটিতে পুনর্নির্দেশ দ্বারা চালিত হয়েছিল /dev/null

  1. আমার বাশ (~.৩ মিনিট)

    real   3m39.775s
    user   1m26.083s
    sys    2m13.400s
    
  2. চোরোবার (বাশ, ~ 0.7 সেকেন্ড)

    real   0m0.732s
    user   0m0.697s
    sys    0m0.037s
    
  3. আমার পার্ল (~ 0.1 সেকেন্ড):

    real   0m0.100s
    user   0m0.093s
    sys    0m0.013s
    

একটি চমত্কারভাবে সংক্ষিপ্ত পদ্ধতির জন্য +1, এটি ফাইলগুলি অন্তর্ভুক্ত ডিরেক্টরি থেকে চালানো উচিত। আমি জানি আমি cdস্ক্রিপ্টের ডিরেক্টরিতে প্রবেশ করতে পারি , তবে কী এমন কোনও পদ্ধতি আছে যার সাহায্যে ফাইলের পথটি অনুসন্ধানে অন্তর্ভুক্ত করা যেতে পারে?
অ্যারোনিকাল

@ অ্যারোনিকাল নিশ্চিত, আপডেট উত্তর দেখুন। আপনি ${source_directory}আপনার স্ক্রিপ্টে যেমন ব্যবহার করেছিলেন তেমন ব্যবহার করতে পারেন ।
টেরডন

অথবা এটি ব্যবহার করুন "$2"এবং দ্বিতীয় আর্গুমেন্ট হিসাবে স্ক্রিপ্টে দিন।
Alexis

আপনার লক্ষ্যগুলির জন্য এটি যথেষ্ট দ্রুত চলেছে দেখুন - এটির মতো প্রচুর ফাইল লকআপের পরিবর্তে একটি ডিরেক্টরি ডিরেক্টরি স্ক্যান করে এটি করা দ্রুততর হবে।
Alexis

1
@ আলেক্সিস হ্যাঁ, আপনি ঠিক বলেছেন। আমি কিছু পরীক্ষা করেছি এবং ইউআইডি / ফাইলের সংখ্যা বৃদ্ধি পেলে এটি খুব ধীর হয়ে যায় । আমি একটি পার্ল পদ্ধতির যোগ করেছি (যা ব্যাশ স্ক্রিপ্টের মধ্যে থেকে একটি লাইনার হিসাবে চালানো যেতে পারে, তাই প্রযুক্তিগতভাবে, এখনও কিছু সৃজনশীল নামকরণের জন্য খোলা থাকলে বাশ দিন) যা আরও দ্রুত।
টেরডন

3

এটি খাঁটি বাশ (অর্থাত্ কোনও বাহ্যিক আদেশ নয়), এবং এটি আমি মনে করতে পারি এটি সবচেয়ে কাক্সিক্ষত দৃষ্টিভঙ্গি।

তবে পারফরম্যান্স-ওয়াইস আপনার বর্তমানে যা আছে তার চেয়ে সত্যিকারের চেয়ে ভাল নয়।

এটি প্রতিটি লাইন থেকে পড়বে path/to/file; প্রতিটি লাইনে জন্য, এটি প্রথম ক্ষেত্র সংরক্ষণ করবে $uuidএবং কে একটি বার্তা ছাপে যদি নকশার সাথে মেলে এমন একটি ফাইল path/to/directory/$uuid*হয় না পাওয়া যায়নি:

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

সাথে কল path/to/script path/to/file path/to/directory

প্রশ্নে নমুনা ফাইল ধারণকারী একটি পরীক্ষা ডিরেক্টরি শ্রেণিবিন্যাসে প্রশ্নের নমুনা ইনপুট ফাইলটি ব্যবহার করে নমুনা আউটপুট:

% 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

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

এখানে ধারণাটি শেলটি আপনার জন্য রিপোর্ট করবে এমন ত্রুটিগুলি সম্পর্কে ভেবে চিন্তার নয়। যদি আপনি <এমন কোনও ফাইল খোলার চেষ্টা করেন যা আপনার শেলটির অস্তিত্ব নেই তবে অভিযোগ করবে। প্রকৃতপক্ষে, এটি আপনার স্ক্রিপ্ট $0এবং লাইন নম্বরটিকে ত্রুটিযুক্ত আউটপুটে ত্রুটিটি যখন ঘটে তখন এটি সংশোধন করে ... এটি ভাল তথ্য যা ডিফল্টরূপে ইতিমধ্যে সরবরাহ করা হয় - সুতরাং বিরক্ত করবেন না।

আপনারও সেই মতো লাইন-বাই-লাইনে ফাইলটি নেওয়ার দরকার নেই - এটি দুর্দান্তভাবে ধীর হতে পারে। এটি একক শটে পুরো জিনিসটিকে একটি সাদা-স্পেস-এর সীমানাযুক্ত তর্কগুলিতে ছড়িয়ে দেয় এবং এটি একবারে দুটি পরিচালনা করে। যদি আপনার ডেটা আপনার উদাহরণের সাথে সামঞ্জস্যপূর্ণ হয় তবে $1সর্বদা আপনার uuid $2হবে এবং আপনার হবে $name। যদি bashআপনার ইউইডিতে কোনও ম্যাচ খুলতে পারে - এবং এর মধ্যে কেবল একটির মিল রয়েছে - তবে printfঘটে। অন্যথায় এটি হয় না এবং শেল কেন স্টাডারকে ডায়াগনস্টিকগুলি লিখেছিল।


1
@ কোস - ফাইল কি বিদ্যমান? যদি না হয়, তবে এটি যেমন ইচ্ছা তেমন আচরণ করে। সাদা স্পেসে বিভক্ত তা unset IFSনিশ্চিত করে $(cat <uuid_file)। শেলগুলি $IFSপৃথকভাবে বিভক্ত হয় যখন এটি কেবল সাদা স্থান নিয়ে গঠিত বা সেট না করে। এই জাতীয় বিভাজন প্রসারণের কোনও নাল ক্ষেত্র কখনই না থাকায় সমস্ত সাদা-স্থানের ক্রমগুলি কেবল একটি একক ক্ষেত্রের সীমানা হিসাবে দাঁড়িয়ে থাকে। যতক্ষণ না প্রতিটি লাইনে কেবল দুটি অ-সাদা-স্পেস পৃথক ক্ষেত্র রয়েছে তার কাজ করা উচিত, আমি মনে করি। ইন bash, যাইহোক। set -fনিশ্চিত করে যে অনাবৃত প্রসারণ গ্লোবগুলির জন্য ব্যাখ্যা করা না যায় এবং সেট + এফ নিশ্চিত করে যে পরবর্তী গ্লোবগুলি রয়েছে are
মাইকসার্ভ

@ কোস - আমি ঠিক এটি ঠিক করেছি। আমার ব্যবহার করা উচিত ছিল না <>কারণ এটি একটি অস্তিত্বহীন ফাইল তৈরি করে। <আমি যেমন বলেছিলাম তেমন রিপোর্ট করবে। যদিও এর সাথে সম্ভাব্য সমস্যাটি - এবং যে কারণে আমি ভুল <>করে প্রথম স্থানে ব্যবহার করেছি - তা হ'ল এটি যদি কোনও পাইপ ফাইল না হয় পাঠকবিহীন বা লাইন-বাফার্ড চর দেবের মতো হয় তবে এটি স্তব্ধ হয়ে যাবে। এটি আরও স্পষ্টভাবে ত্রুটি আউটপুট পরিচালনা করে এবং এড়ানো যায় [ -f "$dir/$1"* ]। আমরা এখানে uuids সম্পর্কে কথা বলছি, এবং তাই এটি কখনও একক ফাইলের চেয়ে বেশি প্রসারিত হওয়া উচিত নয়। এটি দুর্দান্ত লাগছে যদিও এটি কীভাবে স্ট্রার্ডকে ব্যর্থ ফাইলগুলির নাম জানায়।
মাইকসার্ভ

@ কোস - আসলে, আমি মনে করি যে আমি কোনও ফাইল তৈরি করা থেকে বিরত রাখতে ইউলিমিট ব্যবহার করতে পারি এবং <>এখনও সেভাবে ব্যবহারযোগ্য হবে ... <>যদি গ্লোব কোনও ডিরেক্টরিতে প্রসারিত হতে পারে তবে একটি লিনাক্সের উপর পড়তে / লিখতে হবে ব্যর্থ এবং বলুন - একটি ডিরেক্টরি thats।
মাইকসার্ভ

@ কোস - ওহ! আমি দুঃখিত - আমি ঠিক বোবা হয়ে যাচ্ছি - আপনার দুটি ম্যাচ আছে এবং তাই এটি সঠিক কাজ করছে। আমি বোঝাতে চাইছি সেভাবে এটির জন্য ত্রুটি রয়েছে যদি দুটি ম্যাচ হতে পারে তবে এগুলি ইউয়েড হওয়ার কথা - একই গ্লোবের সাথে মেলে এমন 2 টির মতো নাম হওয়ার সম্ভাবনা কখনই পাওয়া উচিত নয়। সম্পূর্ণ ইচ্ছাকৃতভাবে চালিত করে - এবং এটি এমনভাবে অস্পষ্ট যে এটি হওয়া উচিত নয়। তুমি কি বোঝাতে চেয়েছ? গ্লোবটির জন্য ফাইলটির নামকরণ সমস্যাটি নয়, - বিশেষ অক্ষরগুলি এখানে প্রাসঙ্গিক নয় - সমস্যাটি হ'ল bashকেবলমাত্র একটি ফাইলের সাথে মেলে যদি কেবল একটি পুনঃনির্দেশ গ্লোব গ্রহণ করবে। man bashপুনর্নির্মাণের অধীনে দেখুন ।
মাইকসার্ভ

1

আমি যেভাবে এর কাছে যাচ্ছি তা হ'ল প্রথমে ফাইল থেকে uuids পাওয়া, তারপরে ব্যবহার find

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

পাঠযোগ্যতার জন্য,

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

/etc/Passwd, গোষ্ঠী, fstab, এবং THISDOESNTEXIST ফাইলের নাম অনুসন্ধান করে ফাইলগুলির একটি তালিকার উদাহরণ ।

$ 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

যেহেতু আপনি ডিরেক্টরিটি ফ্ল্যাট হিসাবে উল্লেখ করেছেন তাই আপনি -printf "%f\n"কেবল ফাইলের নাম মুদ্রণ করতে বিকল্পটি ব্যবহার করতে পারেন

এটি যা করে না তা হ'ল ফাইলগুলি তালিকাভুক্ত করা। findএর ছোট অসুবিধা হ'ল এটি কোনও ফাইল খুঁজে না পাওয়া সত্ত্বেও আপনাকে জানায় না, কেবল যখন এটি কোনও কিছুর সাথে মেলে। তবে যা করা যায় তা হ'ল আউটপুটটি পরীক্ষা করা - যদি আউটপুট খালি থাকে তবে আমাদের একটি ফাইল নেই missing

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

আরও পঠনযোগ্য:

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

এটি এখানে কীভাবে এটি একটি ছোট স্ক্রিপ্ট হিসাবে সম্পাদন করে:

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

কেউ statবিকল্প হিসাবে ব্যবহার করতে পারে , যেহেতু এটি একটি ফ্ল্যাট ডিরেক্টরি, তবে কোড বেলো সাব-ডাইরেক্টরিগুলির জন্য পুনরাবৃত্তভাবে কাজ করবে না যদি আপনি কখনও সেগুলি যুক্ত করার সিদ্ধান্ত নেন:

$ 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

আমরা যদি statধারণাটি গ্রহণ করি এবং এটির সাথে চালাই তবে আমরা ফাইলের উপস্থিতি নেই কি না তার ইঙ্গিত হিসাবে স্টেটের প্রস্থান কোডটি ব্যবহার করতে পারি। কার্যকরভাবে, আমরা এটি করতে চাই:

$ 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

নমুনা রান:

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
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.