كل برنامج تشغّله من طرفية يمكن أن تُسلَّم له وسائط: فـ gcc -Wall hello.c -o hello يمرّر أربعة منها. وبرامجك أنت في C تقرأ وسائطها عبر وسيطين في main، وتلك هي الآلية كلها - بلا استدعاء مكتبة وبلا إعداد.
البصمة
حتى الآن استخدم كل مثال في هذه المستندات int main(void). والصيغة القياسية الأخرى تأخذ سطر الأوامر:
int main(int argc, char *argv[]) {
/* ... */
}
argc("عدد الوسائط") هو كم وسيطًا هناك، متضمّنًا اسم البرنامج.argv("متّجه الوسائط") مصفوفة من السلاسل النصية. وchar *argv[]تعني "مصفوفة من مؤشّرات إلى char"، أي مصفوفة من سلاسل C النصية.
وchar **argv تسمية مكافئة ستراها بالكثرة نفسها؛ فوسائط المصفوفات تتحوّل إلى مؤشّرات، فالاثنتان تعرّفان الشيء نفسه.
استخدم لوحة Args في المحرّر لتزويد الوسائط (كل حقل وسيط واحد)، ثم شغّل. جرّب ثلاثة حقول: hello وworld و42.
التشريح
تشغيل ./greet Ada Lovelace يُنتج:
| الفهرس | القيمة | |
|---|---|---|
argv[0] | "./greet" | اسم البرنامج كما استُدعي |
argv[1] | "Ada" | أول وسيط حقيقي |
argv[2] | "Lovelace" | ثاني وسيط حقيقي |
argv[3] | NULL | المُنهي |
argc | 3 | العدد متضمّنًا argv[0] |
أربع حقائق تستحقّ الحفظ:
argcلا يقلّ عن 1 عمليًا، لأنargv[0]هو اسم البرنامج. وتبدأ الوسائط الحقيقية عند الفهرس 1، ولهذا تبدأ الحلقات عليها منi = 1.- كل شيء سلسلة نصية.
./prog 42يعطيك"42"، أي المحرفين، لا العدد 42 أبدًا. والتحويل مهمّتك أنت. argv[argc]تساويNULL. مضمون بالمعيار، فيمكنك المرور على المصفوفة دونargcإن فضّلت:for (char **p = argv + 1; *p != NULL; p++).- الصدفة هي من يقسّم الكلمات، لا برنامجك.
./prog "Ada Lovelace"وسيط واحد؛ فعلامات الاقتباس تستهلكها الصدفة ولا تصلargvأبدًا.
التحقّق قبل القراءة
الوسيط الذي لم يُمرَّر ليس سلسلة فارغة - بل هو ذاكرة لا حقّ لك في لمسها. تحقّق دائمًا من argc أولًا:
وثمّة عُرفان في ذلك البرنامج الصغير، وكلاهما قياسي عبر أدوات يونكس:
- تذهب رسالة الاستخدام إلى
stderrلاstdout، لئلّا تلوّث مخرجات تُمرَّر إلى مكان آخر. - قيمة إرجاع غير صفرية من
mainتُبلغ بالفشل لأي شيء شغّل البرنامج. فـ0تعني النجاح؛ وأي شيء آخر يعني أن خطبًا ما وقع. وسكريبتات الصدفة تفحصها.
وطباعة argv[0] بدل اسم مكتوب يدويًا تعني أن الرسالة تطابق الطريقة التي استدعى بها المستخدم البرنامج، حتى لو أُعيدت تسميته أو وُصل إليه بمسار مختلف.
تحويل الوسائط العددية
argv[1] نصّ. ولإجراء حساب عليك تحويله، والدالة المناسبة هي strtol من <stdlib.h>:
أضف 10 و20 و30 ثلاثةَ حقول في لوحة Args، ثم استبدل أحدها بـ banana لترى مسار الخطأ.
الوسيط الثالث لـ strtol هو الأساس: 10 للعشري، و16 للسداسي عشري، أو 0 للاكتشاف التلقائي من بادئة 0x أو صفر في المقدّمة. والثاني هو موضع تخزين مؤشّر إلى أول محرف لم تستهلكه، وهو ما يجعل فحص الأخطاء ممكنًا:
end == textتعني أنها لم تحلّل شيئًا - فالوسيط لم يبدأ برقم.*end != '\0'تعني وجود نص متبقٍّ بعد العدد، فيُرفض"12abc"بدل أن يصبح 12 بصمت.errno == ERANGEتعني أن القيمة فاضت عنlong.
قارن بـ atoi، وهي أقصر ولا تستطيع الإبلاغ عن أي من ذلك:
int n = atoi(argv[1]); /* "banana" -> 0, "0" -> 0, overflow -> undefined */
تُعيد atoi القيمة 0 للإدخال غير الصالح، وهو ما لا يمكن تمييزه عن صفر حقيقي. استخدم strtol (وstrtod للفاصلة العائمة) كلما جاء الإدخال من خارج برنامجك. المزيد في تحويل السلاسل النصية.
آلة حاسبة صغيرة
نجمع كل ذلك - برنامج يأخذ عددًا وعاملًا وعددًا:
جرّب 12 + 30، ثم 7 x 6، ثم 5 / 0.
لاحظ strcmp(op, "+") == 0 بدل op == "+". فمقارنة قيم char * بـ == تقارن العناوين لا المحتويات، وستكون خاطئة حتى مع نصّ متطابق. انظر دوال السلاسل النصية.
واستخدام x بدل * تفصيلة واقعية: فمعظم الصدفات توسّع * المجرّدة إلى قائمة الملفات في الدليل الحالي قبل أن يعمل برنامجك أصلًا. ووضعها بين علامتي اقتباس ('*') ينجح أيضًا، لكن اختيار محرف تتجاهله الصدفة ألطف.
تحليل الرايات
تبدأ الخيارات عرفًا بـ -. وحلقة بسيطة مكتوبة يدويًا تعالج الحالات الشائعة:
جرّبه بأربعة حقول في لوحة Args: -v و-n و3 وhello.
فحص i + 1 >= argc قبل argv[++i] هو السطر المهمّ: فالخيار الذي يتوقّع قيمة يجب أن يتأكّد من وجودها فعلًا، وإلّا فإن -n كآخر وسيط يقرأ ما بعد نهاية المصفوفة. وعلى أنظمة يونكس الحقيقية تقوم getopt من <unistd.h> بكل هذا نيابة عنك، بما في ذلك الخيارات القصيرة المدمجة مثل -vn3؛ ويستحقّ اللجوء إليها بمجرّد أن يتجاوز البرنامج رايتين أو ثلاثًا.
الوسائط في محرّر المتصفح
كتل المحرّر في هذه الصفحة تُترجَم وتُشغَّل نيابةً عنك، ولوحة Args هي حيث تكتب ما كان سيأتي بعد اسم البرنامج في طرفية. وكل حقل تضيفه بـ Add arg يصبح مدخلًا واحدًا بالضبط في argv - فلا صدفة بينهما، ومن ثمّ لا يُقسَّم شيء على المسافات ولا حاجة إلى أي اقتباس: حقل يحتوي hello world يصل وسيطًا واحدًا هو hello world. ولتمرير -n 3 hello، أضف ثلاثة حقول. وترك اللوحة فارغة يعطي argc == 1 - وهذا سبب انطلاق حارس argc < 2 عند أول تشغيل، وسبب كونه يستحقّ الكتابة.
أخطاء شائعة
- قراءة
argv[1]دون فحصargc. سلوك غير معرَّف حين لا تُمرَّر أي وسائط - وهو أشيع انهيار في هذا المجال. - بدء الحلقة من
i = 0. فذلك يعالج اسم البرنامج كأنه بيانات. - معاملة
argv[1]كعدد. إنه نصّ حتى تحوّله. - استخدام
atoiعلى إدخال المستخدم. فلا سبيل لتمييز وسيط سيّئ عن صفر. - مقارنة السلاسل النصية بـ
==. استخدمstrcmp. - الكتابة داخل سلاسل
argv. تعديلها مسموح بالمعيار لكن المساحة محدودة وتعتمد على المنصّة؛ انسخ إلى مخزنك الخاص إن احتجت إلى تغيير شيء.
الأسئلة الشائعة
ما argc وargv في لغة C؟
هما وسيطا int main(int argc, char *argv[]). argc هو عدد وسائط سطر الأوامر متضمّنًا اسم البرنامج؛ وargv مصفوفة من تلك الوسائط كسلاسل نصية. argv[0] هو اسم البرنامج، وargv[1] أول وسيط حقيقي، وargv[argc] يساوي NULL دائمًا.
كيف تمرّر وسائط إلى برنامج C؟
اكتبها بعد اسم الملف التنفيذي في الطرفية: ./program hello 42. يعطي ذلك argc == 3، حيث argv[1] هي السلسلة "hello" وargv[2] هي السلسلة "42". وفي المحرّر في هذه الصفحة، تُكتب الوسائط في لوحة Args بدل الطرفية.
كيف تحوّل وسيط سطر أوامر إلى int في لغة C؟
استخدم strtol: char *end; long n = strtol(argv[1], &end, 10); ثم تحقّق من أن end لم تعد تشير إلى البداية (لم يُحلَّل شيء) وأن *end تساوي '\0' (لا زوائد لاحقة). أما atoi فأقصر لكنه يُعيد 0 لكلٍّ من "0" و"banana"، فلا يستطيع الإبلاغ عن خطأ.
ما argv[0] في لغة C؟
الاسم الذي استُدعي به البرنامج - عادةً ./program أو المسار الكامل. وهو مفيد في رسائل الاستخدام (fprintf(stderr, "usage: %s FILE\n", argv[0])) لتطابق الرسالة الطريقة التي استدعى بها المستخدم البرنامج فعلًا. وقد يكون سلسلة فارغة في حالات غير معتادة، فلا تفترض وجوده.