سؤال

استخدام find . -print0 يبدو أن الطريقة الآمنة الوحيدة للحصول على قائمة بالملفات في Bash بسبب إمكانية أسماء الملفات التي تحتوي على مسافات، خطوط جيودا، علامات الاقتباس إلخ.

ومع ذلك، فأنا أواجه صعوبة في جعل إخراج الإخراج في الواقع مفيدا داخل باش أو مع أدوات مساعدة سطر الأوامر الأخرى. الطريقة الوحيدة التي تمكنت من الاستفادة من الناتج هي عن طريق الأنابيب إلى PIRL، وتغيير ifs Perl إلى NULL:

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

يطبع هذا المثال عدد الملفات الموجودة، وتجنب خطر خطوط الأمل في أسماء الملفات التي تفسد العدد، كما يحدث مع:

find . | wc -l

نظرا لأن معظم برامج سطر الأوامر لا تدعم الإدخال غير المحدد ب Null-Delimited، فأني أرجح أن يكون أفضل شيء هو التقاط إخراج find . -print0 في صفيف باش، كما فعلت في مقتطف بيرل أعلاه، ثم تابع المهمة، كل ما قد يكون.

كيف يمكنني أن أفعل هذا؟

هذا لا يعمل:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

قد يكون سؤال عام أكثر: كيف يمكنني أن أفعل أشياء مفيدة مع قوائم الملفات في باش؟

هل كانت مفيدة؟

المحلول

سرقت بلا خجل من جريج بشفاق:

unset a i
while IFS= read -r -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done < <(find /tmp -type f -print0)

لاحظ أن بناء إعادة توجيه المستخدم هنا (cmd1 < <(cmd2)) يشبه، ولكن ليس تماما نفس خط أنابيب المعتاد (cmd2 | cmd1) - إذا كانت الأوامر عبارة عن أبعاد شل (على سبيل المثال while)، تنفذها إصدار خط الأنابيب في فرقتين، وأي متغيرات تحددها (مثل الصفيف a) فقدت عند الخروج. cmd1 < <(cmd2) يدير فقط CMD2 في علاقة صعبة، لذلك يعيش الصفيف بناء بناءه. تحذير: هذا الشكل من إعادة التوجيه متاح فقط في باش، وليس حتى باش في وضع مضاهاة SH؛ يجب أن تبدأ البرنامج النصي الخاص بك مع #!/bin/bash.

أيضا، لأن خطوة معالجة الملفات (في هذه الحالة، فقط a[i++]="$file", ، ولكن قد ترغب في القيام بشيء ما فانتيير مباشرة في الحلقة) لديه إدخال مدخلاته، لا يمكن استخدام أي أوامر قد تقرأ من Stdin. لتجنب هذا القيد، أميل إلى استخدام:

unset a i
while IFS= read -r -u3 -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

... الذي يمر قائمة الملفات عبر الوحدة 3، بدلا من stdin.

نصائح أخرى

ربما كنت تبحث عن xargs:

find . -print0 | xargs -r0 do_something_useful

يمكن أن يكون الخيار -l 1 مفيدا لك أيضا، مما يجعل Xargs exec do_something_useful مع وسيطة ملف واحدة فقط.

المشكلة الرئيسية هي أنه، أن Delimiter NUL ( 0) عديم الفائدة هنا، لأنه ليس من الممكن تعيين قيمة NUL للقيم. لذلك كمبرمجين جيدين نعتني به، أن المدخلات لبرنامجنا هو شيء قادر على التعامل معه.

أولا، نقوم بإنشاء برنامج صغير، وهذا الجزء بالنسبة لنا:

#!/bin/bash
printf "%s" "$@" | base64

... واتصل به base64str (لا تنسى chmod + x)

ثانيا يمكننا الآن استخدام حلقة بسيطة ومباشرة

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

لذلك الحيلة هي أن سلسلة BASE64 لا تحتوي على علامة تسبب مشكلة في باش - بالطبع XXD أو شيء مماثل يمكن أن تفعل الوظيفة أيضا.

طريقة أخرى لعدد الملفات:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 

منذ باش 4.4، المدمج mapfile لديه -d التبديل (لتحديد محدد، على غرار -d التبديل من read بيان)، ويمكن أن يكون المحدد البايت البايت. وبالتالي، هناك إجابة لطيفة على السؤال في العنوان

التقاط إخراج find . -print0 في صفيف باش

يكون:

mapfile -d '' ary < <(find . -print0)

يمكنك القيام بأمان عد مع هذا:

find . -exec echo ';' | wc -l

(يطبع عنوان جديد لكل ملف / دير موجود، ثم عد الطويلات المطبوعة ...)

أعتقد أن الحلول الأناقة الأكثر أناقة موجودة، لكنني سأرسم هذا واحد. هذا سيعمل أيضا على أسماء الملفات مع مسافات و / أو خطوط جيولاين:

i=0;
for f in *; do
  array[$i]="$f"
  ((i++))
done

يمكنك بعد ذلك، على سبيل المثال إدراج الملفات واحدا تلو الآخر (في هذه الحالة بترتيب عكسي):

for ((i = $i - 1; i >= 0; i--)); do
  ls -al "${array[$i]}"
done

هذه الصفحة يعطي مثال جميل، ومزيد من نرى الفصل 26. في ال دليل البرمجة النصية المتقدمة.

تجنب Xargs إذا استطعت:

man ruby | less -p 777 
IFS=$'\777' 
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) 
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) 
echo ${#array[@]} 
printf "%s\n" "${array[@]}" | nl 
echo "${array[0]}" 
IFS=$' \t\n' 

أنا جديد لكنني أعتقد أن هذه الإجابة؛ آمل أن يساعد شخص ما:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE

هذا مشابه إصدار Stephan202، ولكن يتم وضع الملفات (والدلائل) في صفيف في وقت واحد. ال for حلقة هنا فقط "أشياء مفيدة":

files=(*)                        # put files in current directory into an array
i=0
for file in "${files[@]}"
do
    echo "File ${i}: ${file}"    # do something useful 
    let i++
done

للحصول على عدد:

echo ${#files[@]}

السؤال القديم، ولكن لا أحد اقترح هذه الطريقة البسيطة، لذلك اعتقدت أنني سوف. منحت إذا كانت أسماء الملفات الخاصة بك لها ETX، فلا يحل هذا مشكلتك، لكنني أظن أنه يخدم لأي سيناريو في العالم الحقيقي. يبدو أن محاولة استخدام NULL لتشغيل AFOUL من قواعد معالجة IFS الافتراضية. الموسم لأذواقك مع خيارات البحث ومعالجة الأخطاء.

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"

إجابة جوردون دافيسون رائعة لباش. ومع ذلك، يوجد اختصار مفيد لمستخدمي ZSH:

أولا، ضعك سلسلة في متغير:

A="$(find /tmp -type f -print0)"

بعد ذلك، قم بتقسيم هذا المتغير وتخزينه في صفيف:

B=( ${(s/^@/)A} )

هناك خدعة: ^@ هو حرف الخليج. للقيام بذلك، يجب عليك كتابة CTRL + V متبوعا ب Ctrl + @.

يمكنك التحقق من كل إدخال من $ B يحتوي على القيمة الصحيحة:

for i in "$B[@]"; echo \"$i\"

قد يلاحظ القراء الدقيقوا أن ندعو find قد يتم تجنب الأمر في معظم الحالات باستخدام ** بناء الجملة. علي سبيل المثال:

B=( /tmp/** )

لم يكن BASH أبدا جيدا في التعامل مع أسماء الملفات (أو أي نص حقا) لأنه يستخدم مسافات كمسيح قائمة.

أود أن أوصي باستخدام بيثون مع ش مكتبة بدلا من ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top