متى يكون الوقت المناسب (والوقت الخطأ) لاستخدام الـ backticcks؟

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

  •  02-07-2019
  •  | 
  •  

سؤال

العديد من المبرمجين المبتدئين يكتبون التعليمات البرمجية مثل هذا:

sub copy_file ($$) {
  my $from = shift;
  my $to = shift;

  `cp $from $to`;
}

هل هذا سيء، ولماذا؟هل يجب استخدام backticks على الإطلاق؟إذا كان الأمر كذلك، كيف؟

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

المحلول

لقد ذكر عدد قليل من الأشخاص بالفعل أنه يجب عليك استخدام العلامات الخلفية فقط عندما:

  • تحتاج إلى التقاط (أو قمع) الإخراج.
  • لا توجد وظيفة مضمنة أو وحدة Perl للقيام بنفس المهمة، أو أن لديك سببًا وجيهًا لعدم استخدام الوحدة أو الوحدة المضمنة.
  • يمكنك تطهير المدخلات الخاصة بك.
  • يمكنك التحقق من قيمة الإرجاع.

لسوء الحظ، أشياء مثل التحقق من قيمة الإرجاع على وجه صحيح يمكن أن يكون تحديًا كبيرًا.هل مات بسبب الإشارة؟هل تم تشغيله حتى الاكتمال، لكنه أعاد حالة خروج مضحكة؟الطرق القياسية لمحاولة التفسير $? هي مجرد فظيعة.

أوصي باستخدام IPC::النظام::بسيط الوحدة النمطية capture() و system() وظائف بدلا من backticks.ال capture() تعمل الوظيفة تمامًا مثل العلامات الخلفية، باستثناء ما يلي:

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

تعمل الأوامر أيضًا بشكل متسق عبر أنظمة التشغيل وإصدارات Perl، على عكس إصدار Perl المدمج system() والتي قد لا تتحقق من البيانات الملوثة عند استدعائها باستخدام وسائط متعددة في الإصدارات الأقدم من Perl (على سبيل المثال، 5.6.0 مع وسائط متعددة)، أو التي قد تستدعي الصدفة على أي حال ضمن Windows.

على سبيل المثال، سيحفظ مقتطف التعليمات البرمجية التالي نتائج الاتصال بـ perldoc في عددي، يتجنب الصدفة، ويطرح استثناءً إذا تعذر العثور على الصفحة (نظرًا لأن perldoc يُرجع 1).

#!/usr/bin/perl -w
use strict;
use IPC::System::Simple qw(capture);

# Make sure we're called with command-line arguments.
@ARGV or die "Usage: $0 arguments\n";

my $documentation = capture('perldoc', @ARGV);

IPC::النظام::بسيط هو لغة Perl خالصة، ويعمل على الإصدار 5.6.0 وما فوق، ولا يحتوي على أي تبعيات لا تأتي عادةً مع توزيعة Perl الخاصة بك.(في نظام التشغيل Windows يعتمد الأمر على Win32::الوحدة التي تأتي مع كل من ActiveState وStrawberry Perl).

تنصل:أنا مؤلف IPC::النظام::بسيط, ، لذلك قد أظهر بعض التحيز.

نصائح أخرى

القاعدة بسيطة:لا تستخدم أبدًا العلامات الخلفية إذا كان بإمكانك العثور على وحدة مدمجة للقيام بنفس المهمة، أو إذا كانت وحدة قوية في CPAN ستقوم بذلك نيابةً عنك.غالبًا ما تعتمد Backticcks على تعليمات برمجية غير قابلة للنقل، وحتى إذا قمت بإزالة التلوث من المتغيرات، فلا يزال بإمكانك تعريض نفسك للكثير من الثغرات الأمنية.

أبداً استخدم علامات التحديد الخلفية مع بيانات المستخدم ما لم تحدد بشكل صارم ما هو مسموح به (وليس ما هو غير مسموح به - ستفوتك أشياء)!وهذا خطير جداً.

يجب استخدام Backticks إذا وفقط إذا كنت بحاجة إلى التقاط مخرجات الأمر.خلاف ذلك، يجب استخدام النظام ().وبالطبع، إذا كانت هناك وظيفة Perl أو وحدة CPAN التي تقوم بهذه المهمة، فيجب استخدامها بدلاً من أي منهما.

وفي كلتا الحالتين، هناك أمران يتم تشجيعهما بقوة:

أولاً، تطهير جميع المدخلات: استخدم وضع Taint (-T) إذا تعرض الكود لإدخال محتمل غير موثوق به.حتى لو لم يكن الأمر كذلك، تأكد من التعامل مع (أو منع) الأحرف غير التقليدية مثل المسافة أو الأنواع الثلاثة من علامات الاقتباس.

ثانية، تحقق من رمز الإرجاع للتأكد من نجاح الأمر.فيما يلي مثال لكيفية القيام بذلك:

my $cmd = "./do_something.sh foo bar";
my $output = `$cmd`;

if ($?) {
   die "Error running [$cmd]";
}

هناك طريقة أخرى لالتقاط stdout (بالإضافة إلى معرف المنتج ورمز الخروج) وهي استخدام إي بي سي::فتح3 من الممكن إلغاء استخدام كل من النظام والعلامات الخلفية.

استخدم العلامات الخلفية عندما تريد جمع الإخراج من الأمر.

خلاف ذلك system() يعد خيارًا أفضل، خاصة إذا لم تكن بحاجة إلى استدعاء الصدفة للتعامل مع الأحرف الأولية أو تحليل الأوامر.يمكنك تجنب ذلك عن طريق تمرير قائمة إلى النظام ()، على سبيل المثال system('cp', 'foo', 'bar') (ومع ذلك فمن الأفضل أن تستخدم وحدة لذلك خاص مثال :))

في لغة Perl، هناك دائمًا أكثر من طريقة لفعل أي شيء تريده.النقطة الأساسية في backticcks هي الحصول على الإخراج القياسي لأمر shell في متغير Perl.(في المثال الخاص بك، أي شيء يطبعه الأمر cp سيتم إرجاعه إلى المتصل.) الجانب السلبي لاستخدام العلامات الخلفية في المثال الخاص بك هو أنك لا تتحقق من القيمة المرجعة لأمر shell؛قد يفشل cp ولن تلاحظ ذلك.يمكنك استخدام هذا مع متغير Perl الخاص $?.عندما أرغب في تنفيذ أمر Shell، فإنني أميل إلى استخدامه نظام:

system("cp $from $to") == 0
    or die "Unable to copy $from to $to!";

(لاحظ أيضًا أن هذا سيفشل في أسماء الملفات التي تحتوي على مسافات مضمنة، لكنني أفترض أن هذا ليس الهدف من السؤال.)

فيما يلي مثال مفتعل للأماكن التي قد تكون فيها علامات الرجوع مفيدة:

my $user = `whoami`;
chomp $user;
print "Hello, $user!\n";

وفي الحالات الأكثر تعقيدًا، يمكنك أيضًا استخدام يفتح كأنبوب:

open WHO, "who|"
    or die "who failed";
while(<WHO>) {
    # Do something with each line
}
close WHO;

من صفحة "بيرلوب":

هذا لا يعني أنه يجب عليك الخروج عن طريقك لتجنب backticks عندما تكون الطريقة الصحيحة لإنجاز شيء ما.تم صنع بيرل ليكون لغة غراء ، وأحد الأشياء التي يتلاعب بها معًا هو الأوامر.فقط افهم ما تحصل عليه.

بالنسبة للحالة التي تعرضها باستخدام نسخة الملف ربما تكون الوحدة هي الأفضل.ومع ذلك، للإجابة على سؤالك، عندما أحتاج إلى تشغيل أمر نظام أعتمد عليه عادةً IPC::Run3.يوفر الكثير من الوظائف مثل جمع رمز الإرجاع والمخرجات القياسية والخطأ.

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

my $user = `/bin/whoami`;

أو

my $result = `/bin/cp $from $to`;

يؤدي قول "whoami" أو "cp" فقط إلى خطر تشغيل أمر آخر غير ما قصدته عن طريق الخطأ، إذا تغير مسار المستخدم - وهي ثغرة أمنية قد يحاول مهاجم ضار استغلالها.

مثالك سيء نظرًا لوجود مكونات بيرل مدمجة للقيام بذلك والتي تكون محمولة وعادةً ما تكون أكثر كفاءة من بديل backtick.

يجب استخدامها فقط في حالة عدم وجود بديل مدمج (أو وحدة) لـ Perl.هذا مخصص لمكالمات backticks وsystem().تهدف Backticcks إلى التقاط مخرجات الأمر الذي تم تنفيذه.

من المفترض أن يتم استخدام Backticks فقط عندما تريد التقاط الإخراج.استخدامها هنا "تبدو سخيفة". سيؤدي ذلك إلى دليل على أي شخص ينظر إلى الكود الخاص بك في حقيقة أنك لست على دراية ببيرل.

استخدم علامات الظهر إذا كنت تريد التقاط الإخراج.استخدم النظام إذا كنت تريد تشغيل أمر.إحدى المزايا التي ستكتسبها هي القدرة على التحقق من حالة الإرجاع.استخدم الوحدات حيثما أمكن ذلك لسهولة النقل.في هذه الحالة، File::Copy يناسب الفاتورة.

بشكل عام، فمن الأفضل للاستخدام نظام بدلاً من النقرات الخلفية للأسباب التالية:

  1. نظام يشجع المتصل على التحقق من رمز الإرجاع الخاص بالأمر.

  2. نظام يسمح بتدوين "كائن غير مباشر"، وهو أكثر أمانًا ويضيف مرونة.

  3. ترتبط Backticks ثقافيًا بالبرمجة النصية لـ Shell، والتي قد لا تكون شائعة بين قراء الكود.

  4. تستخدم Backticks الحد الأدنى من بناء الجملة لما يمكن أن يكون أمرًا ثقيلًا.

أحد الأسباب التي قد تجعل المستخدمين يميلون إلى استخدام العلامات الخلفية بدلاً من ذلك نظام هو إخفاء STDOUT عن المستخدم.يتم تحقيق ذلك بسهولة ومرونة أكبر عن طريق إعادة توجيه دفق STDOUT:

my $cmd = 'command > /dev/null';
system($cmd) == 0 or die "system $cmd failed: $?"

علاوة على ذلك، يتم التخلص من STDERR بسهولة:

my $cmd = 'command 2> error_file.txt > /dev/null';

في المواقف التي يكون من المنطقي فيها استخدام العلامات الخلفية، أفضل استخدام س س {} من أجل التأكيد على أن هناك أمرًا ثقيل الوزن يحدث.

ومن ناحية أخرى، فإن وجود طريقة أخرى للقيام بذلك يمكن أن يساعد حقًا.في بعض الأحيان تحتاج فقط إلى معرفة ما يطبعه الأمر إلى STDOUT.تعتبر Backticcks، عند استخدامها كما هو الحال في نصوص Shell، الأداة المناسبة للمهمة.

بيرل لديه شخصية منقسمة.من ناحية، فهي لغة برمجة نصية رائعة يمكن أن تحل محل استخدام الصدفة.في هذا النوع من استخدام مراقبة النتيجة لمرة واحدة، تكون النقرات الخلفية ملائمة.

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

وبصرف النظر عن ما سبق، يجب استخدام وظيفة النظام عندما لا يتم استخدام مخرجات الأمر.

Backticks مخصصة للهواة.الحل المضاد للرصاص هو "فتح الأنبوب الآمن" (انظر "man perlipc").يمكنك تنفيذ الأمر الخاص بك في عملية أخرى، مما يسمح لك بتنفيذ الأمر أولاً باستخدام STDERR، وsetuid، وما إلى ذلك.مزايا:نعم هو كذلك لا اعتمد على الصدفة لتحليل @ARGV، على عكس open("$cmd $args|")، الذي لا يمكن الاعتماد عليه.يمكنك إعادة توجيه STDERR وتغيير امتيازات المستخدم دون تغيير سلوك برنامجك الرئيسي.هذا أكثر تفصيلاً من العلامات الخلفية ولكن يمكنك لفه في وظيفتك الخاصة مثل run_cmd($cmd,@args);


sub run_cmd {
  my $cmd = shift @_;
  my @args = @_;

  my $fh; # file handle
  my $pid = open($fh, '-|');
  defined($pid) or die "Could not fork";
  if ($pid == 0) {
    open STDERR, '>/dev/null';
    # setuid() if necessary
    exec ($cmd, @args) or exit 1;
  }
  wait; # may want to time out here?
  if ($? >> 8) { die "Error running $cmd: [$?]"; }
  while (<$fh>) {
    # Have fun with the output of $cmd
  }
  close $fh;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top