ملخص معالجة معلمات سطر الأوامر في C/C++
حينما كنت أتصفح شفرة نواة Linux مؤخرًا، لفت انتباهي كيفية معالجة النواة لمعلمات الوحدات (moduleparam)، ووجدتها بارعة للغاية، مما دفعني للتفكير في كيفية معالجة معلمات سطر الأوامر في لغة C بشكل أفضل. يمكن العثور على جميع الشفرات المستخدمة في هذه المقالة هنايدعم الكود تشغيل وتصحيح الأخطاء على نظامي Windows وLinux وMac OS X ، وتوجد إرشادات تفصيلية لعملية الترجمة في ملف README.md.
getenv
المكتبة القياسية توفّر لنا دالة getenv
، من واحدة تعنيها بالحرف، هذه الدالة تُستخدم للحصول على المتغيّرات البيئية، لذا بمجرد أن نقوم بتعيين المتغيّرات البيئية المطلوبة مسبقًا ، نستطيع استرجاعها في البرنامج، مما يمكننا من تمرير البارامترات إلى البرنامج غير مباشرة. دعونا ننظر إلى هذا المقطع الكودعفوًا، لم أستطع فهم النص الذي قمت بإرساله. هل يمكنكم إعادة صياغته أو تقديم سياق إضافي للمساعدة في ترجمته؟
ترجمة النص إلى اللغة العربية:
إعلان وظيفة getenv
كما هو موجود في 4(#__codelineno-0-10)(#__codelineno-0-15)المهمة تتمثل في الحصول على قيمتي متغيري بيئة مختلفين، وإذا كان المتغير صالحًا فإنه يُطبع قيمة المتغير. يجب مراعاة أن getenv
يُرجع دائمًا سلاسل نصية، ويتعين على المستخدم تحويل أنواع القيم الرقمية يدويًا، لذا فإن الاستخدام ليس مرنًا بما فيه الكفاية. اُجرِ الترجمة واستعرض النتائج:
لا يوجد ترجمة محددة لـ "Windows 下" في هذا السياق.
Linux 下:
الناتج:
getopt
لينكس يوفر لنا مجموعة من الوظائف getopt، getopt_long، getopt_long_only
لمعالجة الوظائف التي تم تمريرها عبر سطر الأوامر، تصريحات هذه الوظائف الثلاثة هي:
getopt
لا يمكنه معالجة الوسائط القصيرة (أي الوسائط من حرف واحد)، أما getopt_long, getopt_long_only
فيمكنهما معالجة الوسائط الطويلة. يمكنك العثور على شرح مفصل للدوال في دليل Linux. الآن سنشرح استخدام getopt
و getopt_long
من خلال الأمثلة.
الملاحظة الهامة هي أنه لا توجد هذه الدالة في نظام Windows، لذا بحثت عن نسخة من الشفرة المصدرية يمكن ترجمتها على نظام Windows، وأجريت بعض التعديلات البسيطة عليها. يمكن العثور على الشفرة في هذا الرابطI'm sorry, but the text you provided does not contain any content to be translated.
لنركز الآن على تحليل كيفية استخدام getopt_long
، حيث تكون البارميترات الثلاثة الأولى لـ getopt_long
مطابقة تماما لـ getopt
، وهي عبارة عن: عدد معلمات سطر الأوامر argc
، مصفوفة معلمات سطر الأوامر argv
، وصيغة البارميترات القصيرة optstring
. صيغة otpstring
تحتوي على أحرف بارميترات قصيرة واحدة تلو الأخرى، وتضاف بعدها نقطة وفاصلة :
للإشارة إلى وجود بارميتر، ونقطتان وفاصلتان ::
للإشارة إلى بارميتر اختياري. على سبيل المثال، في السطر رقم 19، نجد تعريف لصيغة البارميترات القصيرة، حيث إن البارميتر b
لا يحتاج إلى بارميتر إضافي، والبارميتر a
يتطلب بارميترًا إضافيًا، والبارميتر c
قابل للاختيار.
المعلمات الأخيرتان لدالة getopt_long
تستخدمان للتعامل مع الخيارات الطويلة، حيث يكون هيكل option
كالتالي:
struct option {
const char *name; // الاسم الطويل للمعلمة
int has_arg; // 是否带额外参数
ترجمة النص إلى اللغة العربية:
int *flag; // تحديد كيفية إرجاع نتيجة استدعاء الدالة
int val ؛ // القيمة المُرجعة
};
الاسم
بطول حرف واحد.
has_arg
يمكن تعيينها لno_argument، required_argument، optional_argument
، تُعنى على التوالي بدون مُعطى، مع مُعطى، مع مُعطى إختياري.
flag
و val
يتم استخدامهما معًا. إذا كان flag = NULL
, ستقوم getopt_long
بإرجاع val
مباشرة، وإذا كان flag
يشير إلى عنوان صحيح، سيقوم getopt_long
بتنفيذ عملية مماثلة لـ *flag = val
, تعيين قيمة المتغير الذي يشير إليه flag
إلى قيمة val
.
إذا وُجدت getopt_long
المعلمات القصيرة المُطابقة، سيُعيد قيمة الحرف لتلك المُعلمة القصيرة. إذا تم العثور على مُعلمات طويلة مُطابقة، سيُعيد val
(flag = NULL
) أو 0
(flag != NULL؛ *flag = val;
). إذا واجه حرفًا ليس بمُعلمة، سيُرجع ?
. وفي حال تم معالجة جميع المُعلمات، سيُرجع -1
.
من خلال استغلال خاصية قيم العودة، يمكننا إنشاء تأثير يجعل القيم الطويلة والقصيرة تحمل نفس المعنى، على سبيل المثال، عندما نقوم بتعيين القيمة الأولى لـ long_options
إلى add
، ونقوم بتعيين قيمة val
لتكون الحرف القصير للمعامل 'a'
، فيمكن لـ --add
و-a
أن يُعالَجا ضمن نفس الفرع البرمجي عند العودة، ويتم معاملتهما كمعنى واحد.
القطعة الأخيرة من اللغز هي استخدام optind
و optarg
، حيث optind
هو موقع العامل التالي في argv
الذي يجب معالجته ، بينما يشير optarg
إلى سلسلة العوامل الإضافية.
لتحميل وتشغيل الشيفرات:
$ .\getopt_test -a 1 -b -c4 --add 2 --verbose --verbose=3 -123 -e --e
option a with value '1'
option b
option c with value '4'
option a with value '2'
option verbose
option verbose with arg 3
option 1
option 2
option 3
.\getopt_test: invalid option -- e
.\getopt_test: unrecognized option `--e'
يشير -a
و--add
إلى المعنى نفسه، حيث يتم وضع الوسيطات الاختيارية للوسائط القصيرة مباشرة بعد الوسيطة، مثل -c4
، بينما تحتاج الوسيطات الطويلة للوسائط الاختيارية إلى وجود علامة يساوي، مثل --verbose=3
.
mobuleparam
حسنًا، وصلنا أخيرًا إلى الطريقة التي قامت بإثارة هذه المقالة، حيث استخدم نواة Linux طريقة ذكية جدًا لتمرير المعلمات لوحدات النواة، وهذه الطريقة تُسمى moduleparam
. سأشرح هنا بإيجاز كيفية عمل moduleparam
في نواة Linux، ولشرح مفصل يُمكن الرجوع إلى الشيفرة البرمجية. على الرغم من أنني اقتبست بعض طرق التعامل مع moduleparam
، إلا أن هناك بعض الاختلافات بينها وبين moduleparam
في نواة Linux، وللتمييز سأشير إلى طريقتي بـ small moduleparam
، أما النواة الخاصة بنظام Linux فستظل تسمى moduleparam
.
لنلقَ نظرة على كيفية استخدام moduleparam
، ابدأ بتعريفه في الوحدة:
ثم عند تحميل الوحدة ، أدخل المعلمات:
تم تعيين المتغير enable_debug
بشكل صحيح إلى 1
، مما يجعل استخدامه سهلًا جدًا، ويتطلب قليلًا من الشفرة الإضافية. يمكن كتابة الشفرة بطريقة قصيرة وأنيقة، دون الحاجة لكتابة العديد من التكرارات والتحققات الشرطية كما في getenv
و getopt
. بالإضافة إلى ذلك، يتضمن تحويل الأنواع تلقائيًا. لذلك، عندما رأيته، تساءلت إذا كان بإمكاني استخدام هذه الطريقة في معالجة معلمات سطر الأوامر، سيكون ذلك أفضل بكثير.
لنلق نظرة على تنفيذ وحدة المعلمة النواة:
"module_param" هو ماكرو، يقوم في الحقيقة بإنشاء هيكل kernel_param
يمكن إرجاعه للمتغير المُدخل، وهذا الهيكل يحتوي على المعلومات الكافية للوصول وضبط المتغير، مثلا في السطور 20-24، ومن ثم يتم وضع الهيكل ضمن قسم يُسمى __param
(باستخدام __section__("__param")
). بعد حفظ الهيكل بشكل صحيح، يقوم النواة بالبحث عن مكان قسم __param
في ملف elf وعدد الهياكل فيه، ثم ضبط قيم كل معلمة بناءً على الاسم و param_set_fn
خلال تحميل الوحدة. كيفية العثور على قسم مُحدد بالاسم تعتمد على النظام الأساسي، ففي نواة Linux يتم التعامل مع ملفات elf، وتوفر Linux أمر readelf
لعرض معلومات ملفات elf، ويمكن للمهتمين الاطلاع على معلومات تعليمات الاستخدام لـ readelf
.
النص المُراد ترجمته إلى اللغة العربية هو:
يُقال أعلاه أن التعامل مع نواة Linux يعتمد على النظام الأساسي، ولكنني أريد أسلوبًا للتعامل مع المعلمات غير المتعلق بالنظام، لذا يجب علينا تغيير طريقة moduleparam
الأصلية بشكل بسيط، عن طريق إزالة إعلان __section(" __param")
، حيث أننا لا نرغب في عملية قراءة ملف elf section
بشكل معقد. دعونا نلقي نظرة على كيفية الاستخدام بعد التعديل:
للحفاظ على هيكلية كل انعكاس، قمت بإضافة ماكرو init_module_param(num)
لتعريف مساحة حفظ الهيكل، حيث يُعتبر num
عدد المعلمات. إذا تجاوز عدد المعلمات المعلن عنها num
، سيُشعر البرنامج بحدوث خطأ تأكيد. تم حذف المعلمة الأخيرة التي تُعبر عن صلاحيات الوصول في تعريف module_param
، على عكس النسخة الأصلية. تمت إضافة الـماكرو module_param_bool
لمعالجة المتغيرات من نوع bool
، وهذا الأمر غير ضروري في نظام Linux لأنه يستخدم وظيفة المضمنة __builtin_types_compatible_p
في مترجم gcc للتحقق من نوع المتغيرات. للأسف، لا يتوفر هذا الدالة في MSVC، لذا تمت إزالتها واستبدالها بماكرو جديد. أما الـماكرو module_param_array
و module_param_string
، فيتعاملان مع مصفوفات وسلاسل نصوص، وكانت هاتان الوظيفتان متاحتان في النسخة الأصلية أيضًا.
بمجرد إكمال تعريف المعلمات، يتعين معالجة المعلمات الواردة عن طريق استخدام الوسيط parse_params
مع إدخال argc، argv
، حيث يكون البرمتر الثالث مؤشر دالة رد الاستدعاء للمعلمات غير المعروفة، يمكن تمرير قيمة NULL
، وبذلك ستُنهى معالجة المعلمات في حدود المعلمات الموجودة، ثم يُرجع رمز الخطأ.
ترجمة النص إلى اللغة العربية:
تشغيل وتنفيذ الشيفرة:
.\moduleparam_test.exe error=0 test=101 btest=1 latest=1,2,3 strtest=\"Hello World!\"
Parsing ARGS: error=0 test=101 btest=1 latest=1,2,3 strtest="Hello World!"
find unknown param: error
test = 101
btest = Y
latest = 1,2,3
strtest = Hello World!
يمكن رؤية أن القيم الرقمية والمصفوفات والسلاسل يمكن قراءتها بشكل صحيح وتحويل تنسيقها، في حال واجهت أي من الباراميترات صعوبة في التحويل، سيتم إرجاع رمز الخطأ وطباعة المعلومات ذات الصلة. يمكننا بسهولة إضافة بضعة أسطر من الشيفرة، لإكمال قراءة الباراميترات ومعالجة التحويل، وستكون العملية أنيقة للغاية. يمكن الإطلاع على التنفيذ التفصيلي مباشرة من خلال الشيفرة هناI'm sorry, but I can't provide a translation for that text as it does not contain any content to be translated.
ملخص
لقد قمنا هذه المرة بتلخيص ثلاث طرق لمعالجة معاملات سطر الأوامر في C/C++، وهي getenv
، getopt
، و moduleparam
. لكل من هذه الطرق خصائصها الخاصة، ونستطيع اختيار الطريقة المناسبة بناءً على احتياجاتنا الفعلية في المستقبل.
getenv
هي وظيفة مدمجة متعددة المنصات ويمكن استخدامها مباشرة، ومع ذلك فهي قديمة نوعًا ما، وتستخدم المتغيرات البيئية، مما يؤدي إلى تلويث البيئة بشكل معين. من الأفضل تنظيف المتغيرات البيئية غير الضرورية قبل كل استخدام لتجنب ترك بقايا الإعدادات السابقة وتلويث البيئة.
getopt
مدعوم بشكل أصلي في منصة Linux وليس في Windows. لذلك، يتطلب تضمين الشفرة التنفيذية ليمكن استخدامه على منصات مختلفة. عملية تمرير المعلمات متوافقة مع معايير تمرير الأوامر في Linux، تدعم المعلمات الاختيارية. ولكن استخدامه يعتبر معقدًا قليلا، حيث يتطلب عادةً حلقات وشروط لمعالجة المعلمات المختلفة، ولا يتفاعل بشكل جيد مع المعلمات من نوع الأرقام.
"moduleparam" هو أداة لمعالجة معلمات سطر الأوامر مستوحاة من تنفيذ "moduleparam" في نواة Linux. تدعم الاستخدام عبر الأنظمة الأساسية، سهلة الاستخدام، وتسمح بتحويل أنواع مختلفة من المعلمات، لكن العيب هو أن كل معلمة تتطلب متغيرًا مقابلاً.
Original: https://wiki.disenone.site/ar
This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.
Visitors. Total Visits. Page Visits.
هذه المشاركة تمت ترجمتها باستخدام ChatGPT، يرجى تقديم ردود فعليرجى إشارة إلى أية نقص أو تفاصيل غير مذكورة.