كيف يمكنني استخدام xargs لنسخ الملفات التي تحتوي أسماؤها على مسافات وعلامات اقتباس؟

StackOverflow https://stackoverflow.com/questions/143171

سؤال

أحاول نسخ مجموعة من الملفات الموجودة أسفل الدليل ويحتوي عدد من الملفات على مسافات وعلامات اقتباس مفردة في أسمائها.عندما أحاول الخيط معًا find و grep مع xargs, ، أحصل على الخطأ التالية:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

هل هناك أي اقتراحات لاستخدام أكثر قوة لـ xargs؟

هذا قيد التشغيل نظام التشغيل Mac OS X 10.5.3 (الفهد) مع BSD xargs.

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

المحلول

يمكنك الجمع بين كل ذلك في واحد find يأمر:

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

سيتعامل هذا مع أسماء الملفات والأدلة التي تحتوي على مسافات.يمكنك استخدام -name للحصول على نتائج حساسة لحالة الأحرف.

ملحوظة:ال -- تم تمرير العلم إلى cp يمنعه من معالجة الملفات بدءًا من - كخيارات.

نصائح أخرى

find . -print0 | grep --null 'FooBar' | xargs -0 ...

لا أعرف ما إذا كان grep يدعم --null, ، ولا سواء xargs يدعم -0, ، على Leopard، ولكن على GNU كل شيء جيد.

أسهل طريقة لفعل ما يريده الملصق الأصلي هي تغيير المحدد من أي مسافة بيضاء إلى حرف نهاية السطر فقط كما يلي:

find whatever ... | xargs -d "\n" cp -t /var/tmp

يعد هذا أكثر كفاءة لأنه لا يقوم بتشغيل "cp" عدة مرات:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

وقعت في نفس المشكلة.وإليك كيف قمت بحلها:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

إستعملت sed لاستبدال كل سطر من الإدخال بنفس السطر، ولكن محاطًا بعلامات اقتباس مزدوجة.من sed صفحة الرجل "...يتم استبدال علامة العطف (``&'') التي تظهر في الاستبدال بالسلسلة المطابقة لـ RE..." -- في هذه الحالة، .*, ، الخط بأكمله.

هذا يحل xargs: unterminated quote خطأ.

تعمل هذه الطريقة على نظام التشغيل Mac OS X الإصدار 10.7.5 (أسد):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

لقد اختبرت أيضًا بناء الجملة الدقيق الذي نشرته.وقد نجح ذلك أيضًا على الإصدار 10.7.5.

فقط لا تستخدم xargs.إنه برنامج أنيق لكنه لا يسير على ما يرام find عندما تواجه حالات غير تافهة.

هنا حل محمول (POSIX)، أي.واحد لا يتطلب find, xargs أو cp ملحقات جنو المحددة:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

لاحظ النهاية + بدلا من المعتاد ;.

هذا الحل:

  • يتعامل بشكل صحيح مع الملفات والأدلة التي تحتوي على مسافات مضمنة أو أسطر جديدة أو أي أحرف غريبة.

  • يعمل على أي نظام Unix أو Linux، حتى تلك التي لا توفر مجموعة أدوات GNU.

  • لا يستخدم xargs وهو برنامج جميل ومفيد، ولكنه يتطلب الكثير من التغيير والتبديل والميزات غير القياسية للتعامل معه بشكل صحيح find انتاج.

  • هو أيضا أكثر فعالية (يقرأ أسرع) من الإجابات المقبولة ومعظم الإجابات الأخرى إن لم يكن كلها.

لاحظ أيضاً أنه رغم ما ورد في بعض الردود أو التعليقات الأخرى نقلاً {} لا فائدة منه (إلا إذا كنت تستخدم الملف الغريب fishصدَفَة).

فكر في استخدام خيار سطر الأوامر --null لـ xargs مع خيار -print0 في البحث.

بالنسبة لأولئك الذين يعتمدون على الأوامر، بخلاف البحث، على سبيل المثال ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
find | perl -lne 'print quotemeta' | xargs ls -d

أعتقد أن هذا سيعمل بشكل موثوق مع أي حرف باستثناء تغذية الأسطر (وأظن أنه إذا كان لديك خلاصات أسطر في أسماء ملفاتك، فستواجه مشاكل أسوأ من هذه).إنه لا يتطلب أدوات GNU، بل يتطلب لغة Perl فقط، لذلك يجب أن يعمل إلى حد كبير في أي مكان.

لقد وجدت أن بناء الجملة التالي يعمل بشكل جيد بالنسبة لي.

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200

في هذا المثال، أبحث عن أكبر 200 ملف يزيد حجمها عن 1,000,000 بايت في نظام الملفات المثبت على "/usr/pcapps".

يقوم سطر Perl بين "find" و"xargs" بالهروب/الاقتباس لكل فارغ بحيث يقوم "xargs" بتمرير أي اسم ملف يحتوي على فراغات مضمنة إلى "ls" كوسيطة واحدة.

انتبه إلى أن معظم الخيارات التي تمت مناقشتها في الإجابات الأخرى ليست قياسية على الأنظمة الأساسية التي لا تستخدم أدوات GNU المساعدة (Solaris، AIX، HP-UX، على سبيل المثال).انظر بوسيكس مواصفات سلوك xargs "القياسي".

أجد أيضًا أن سلوك xargs حيث يقوم بتشغيل الأمر مرة واحدة على الأقل، حتى بدون إدخال، يكون مصدر إزعاج.

لقد كتبت نسختي الخاصة من xargs (xargl) للتعامل مع مشاكل المسافات في الأسماء (فقط الأسطر الجديدة منفصلة - على الرغم من أن "العثور على ..."تعتبر مجموعة -print0' و'xargs -0' رائعة جدًا نظرًا لأن أسماء الملفات لا يمكن أن تحتوي على أحرف ASCII NUL '\0'.إن ملف xargl الخاص بي ليس مكتملاً بالقدر الذي يجب أن يكون يستحق النشر - خاصة وأن GNU لديه تسهيلات جيدة على الأقل.

باستخدام Bash (وليس POSIX) يمكنك استخدام عملية الاستبدال للحصول على السطر الحالي داخل متغير.يمكّنك هذا من استخدام علامات الاقتباس للهروب من الأحرف الخاصة:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)

بالنسبة لي، كنت أحاول أن أفعل شيئًا مختلفًا بعض الشيء.كنت أرغب في نسخ ملفات .txt الخاصة بي إلى مجلد tmp الخاص بي.تحتوي أسماء ملفات .txt على مسافات وأحرف الفاصلة العليا.لقد نجح هذا على جهاز Mac الخاص بي.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/

إذا كانت إصدارات find وxarg على نظامك لا تدعم ذلك -print0 و -0 مفاتيح التبديل (على سبيل المثال AIX find وxargs) يمكنك استخدام هذا الرمز الرائع المظهر:

 find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest

هنا سوف يعتني sed بالهروب من المسافات والاقتباسات الخاصة بـ xargs.

تم اختباره على AIX 5.3

لقد قمت بإنشاء برنامج نصي صغير محمول يسمى "xargsL" حول "xargs" والذي يعالج معظم المشكلات.

على عكس xargs، يقبل xargsL اسم مسار واحد لكل سطر.قد تحتوي أسماء المسارات على أي حرف باستثناء (من الواضح) السطر الجديد أو بايت NUL.

لا يُسمح بالاقتباس أو دعمه في قائمة الملفات - قد تحتوي أسماء الملفات الخاصة بك على جميع أنواع المسافات البيضاء والخطوط المائلة العكسية والعلامات الخلفية وأحرف البدل الصدفية وما شابه - سيعمل xargsL على معالجتها كأحرف حرفية، دون حدوث أي ضرر.

كميزة إضافية، سوف يقوم xargsL بذلك لا قم بتشغيل الأمر مرة واحدة إذا لم يكن هناك إدخال!

لاحظ الفرق:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

سيتم تمرير أي وسيطات يتم تقديمها إلى xargsL إلى xargs.

إليك البرنامج النصي لـ POSIX Shell "xargsL":

#! /bin/sh
# Line-based version of "xargs" (one pathname per line which may contain any
# amount of whitespace except for newlines) with the added bonus feature that
# it will not execute the command if the input file is empty.
#
# Version 2018.76.3
#
# Copyright (c) 2018 Guenther Brunthaler. All rights reserved.
#
# This script is free software.
# Distribution is permitted under the terms of the GPLv3.

set -e
trap 'test $? = 0 || echo "$0 failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\\&/g' | xargs ${1+"$@"}
fi

ضع البرنامج النصي في دليل ما في $PATH الخاص بك ولا تنس ذلك

$ chmod +x xargsL

البرنامج النصي هناك لجعله قابلاً للتنفيذ.

نسخة بيرل من bill_starr لن يعمل بشكل جيد مع الأسطر الجديدة المضمنة (يتعامل فقط مع المسافات).لأولئك على سبيل المثال.Solaris حيث لا تتوفر لديك أدوات GNU، قد يكون هناك إصدار أكثر اكتمالاً (باستخدام sed)...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

اضبط وسيطات البحث و grep أو الأوامر الأخرى حسب حاجتك، لكن sed سيصلح الأسطر الجديدة/المسافات/علامات التبويب المضمنة.

إستعملت إجابة بيل ستار تم تعديله قليلاً على سولاريس:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

سيؤدي هذا إلى وضع علامات الاقتباس حول كل سطر.لم أستخدم الخيار "-l" على الرغم من أنه قد يساعد.

قد تحتوي قائمة الملفات التي كنت أستخدمها على "-"، ولكن ليس أسطرًا جديدة.لم أستخدم ملف الإخراج مع أي أوامر أخرى لأنني أرغب في مراجعة ما تم العثور عليه قبل أن أبدأ في حذفها على نطاق واسع عبر xargs.

لقد تعاملت مع هذا قليلاً، وبدأت أفكر في تعديل xargs، وأدركت أنه بالنسبة لنوع حالة الاستخدام التي نتحدث عنها هنا، فإن إعادة التنفيذ البسيطة في Python هي فكرة أفضل.

لسبب واحد، وجود ما يقرب من 80 سطرًا من التعليمات البرمجية لكل شيء يعني أنه من السهل معرفة ما يحدث، وإذا كان هناك حاجة إلى سلوك مختلف، فيمكنك فقط اختراقه وتحويله إلى برنامج نصي جديد في وقت أقل مما يستغرقه الحصول عليه رد في مكان ما مثل Stack Overflow.

يرى https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs و https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py.

باستخدام yargs كما هو مكتوب (وتثبيت Python 3) يمكنك كتابة:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

للقيام بنسخ 203 ملفات في وقت واحد.(هنا 203 هو مجرد عنصر نائب بالطبع، واستخدام رقم غريب مثل 203 يوضح أن هذا الرقم ليس له أهمية أخرى.)

إذا كنت تريد شيئًا أسرع حقًا ودون الحاجة إلى لغة Python، فاستخدم zarcs و yargs كنماذج أولية وأعد كتابتها بلغة C++ أو C.

قد تحتاج إلى دليل grep Foobar مثل:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

تحدي الإطار - أنت تسأل عن كيفية استخدام xargs.الجواب هو:لا تستخدم xargs، لأنك لا تحتاج إليه.

ال التعليق بواسطة user80168 يصف طريقة للقيام بذلك مباشرةً باستخدام cp، دون استدعاء cp لكل ملف:

find . -name '*FooBar*' -exec cp -t /tmp -- {} +

هذا يعمل بسبب:

  • ال cp -t تسمح العلامة بإعطاء الدليل الهدف بالقرب من بداية cp, ، وليس قرب النهاية.من man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • ال -- يقول العلم cp لتفسير كل شيء بعده كاسم ملف، وليس علامة، لذلك تبدأ الملفات بـ - أو -- لا تخلط بين cp;ما زلت بحاجة إلى هذا لأن -/-- يتم تفسير الشخصيات بواسطة cp, ، بينما يتم تفسير أي أحرف خاصة أخرى بواسطة الصدفة.

  • ال find -exec command {} + المتغير يفعل بشكل أساسي نفس xargs.من man find:

   -exec command {} +                                                     
         This  variant  of the -exec action runs the specified command on
         the selected files, but the command line is built  by  appending
         each  selected file name at the end; the total number of invoca‐
         matched  files.   The command line is built in much the same way
         that xargs builds its command lines.  Only one instance of  `{}'
         is  allowed  within the command, and (when find is being invoked
         from a shell) it should be quoted (for example, '{}') to protect
         it  from  interpretation  by shells.  The command is executed in
         the starting directory.  If any invocation  returns  a  non-zero
         value  as exit status, then find returns a non-zero exit status.
         If find encounters an error, this can sometimes cause an immedi‐
         ate  exit, so some pending commands may not be run at all.  This
         variant of -exec always returns true.

باستخدام هذا في البحث مباشرة، يؤدي هذا إلى تجنب الحاجة إلى استدعاء توجيه الإخراج أو الصدفة، بحيث لا داعي للقلق بشأن أي أحرف سيئة في أسماء الملفات.

إذا كنت تستخدم Bash، فيمكنك التحويل com.stdout إلى مجموعة من الخطوط mapfile:

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

الفوائد هي:

  • إنه مدمج، لذا فهو أسرع.
  • قم بتنفيذ الأمر مع جميع أسماء الملفات في وقت واحد، لذلك فهو أسرع.
  • يمكنك إلحاق وسائط أخرى بأسماء الملفات.ل cp, ، بامكانك ايضا:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    ومع ذلك، بعض الأوامر لا تملك مثل هذه الميزة.

العيوب:

  • ربما لا يتم قياسه بشكل جيد إذا كان هناك عدد كبير جدًا من أسماء الملفات.(الحد؟لا أعرف، لكنني اختبرت ملف قائمة بحجم 10 ميغابايت يتضمن أكثر من 10000 اسم ملف دون أي مشكلة، ضمن نظام Debian)

حسنًا...من يدري ما إذا كان Bash متاحًا على OS X؟

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