C: الإدخال/الإخراج المتقدم وسطر الأوامر
وسيطات سطر الأوامر كالطلب في مطعم — تخبر البرنامج بما تريد (argv)، ويحسب البرنامج عدد العناصر المطلوبة (argc)، ثم يُعد طلبك.
1. وسيطات سطر الأوامر
(1) argc و argv
يمكن للدالة main أن تستقبل معاملين:
int main(int argc, char *argv[])
argc: عدد الوسيطات، 1 على الأقل (اسم البرنامج نفسه)argv: متجه الوسيطات (مصفوفة سلاسل)،argv[0]هو اسم البرنامج
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Argument count: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
التشغيل:
./program hello world 123
Argument count: 4
argv[0] = ./program
argv[1] = hello
argv[2] = world
argv[3] = 123
atoi أو atol أو strtol.
(2) تحليل الوسيطات عمليًا
في المشاريع الفعلية، تستخدم وسيطات سطر الأوامر عادةً بادئة - للخيارات:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
int verbose = 0;
int count = 1;
const char *filename = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
count = atoi(argv[++i]);
} else if (argv[i][0] != '-') {
filename = argv[i];
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return 1;
}
}
if (filename == NULL) {
fprintf(stderr, "Usage: %s [-v] [-n count] filename\n", argv[0]);
return 1;
}
printf("File: %s, Count: %d, Verbose: %s\n",
filename, count, verbose ? "on" : "off");
return 0;
}
getopt التي تتعامل مع تركيبات الخيارات القصيرة والطويلة.
2. متغيرات البيئة
(1) getenv
char *getenv(const char *name);
تُرجع قيمة متغير البيئة، أو NULL إذا لم يكن موجودًا.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *path = getenv("PATH");
if (path != NULL) {
printf("PATH = %s\n", path);
}
const char *home = getenv("HOME");
if (home != NULL) {
printf("HOME = %s\n", home);
}
return 0;
}
(2) المتغير الخارجي environ
تعرّف C متغيرًا شاملًا environ يشير إلى مصفوفة من سلاسل متغيرات البيئة جميعها:
#include <stdio.h>
extern char **environ;
int main(void) {
char **env = environ;
while (*env) {
printf("%s\n", *env);
env++;
}
return 0;
}
3. معالجة الأخطاء بـ errno
(1) آلية errno
عندما تواجه دوال المكتبة القياسية أخطاء، لا تنهار عادةً مباشرة. بل تضبط المتغير الشامل errno وتُرجع قيمة خطأ:
#include <errno.h>
لا يتم تصفير errno تلقائيًا بعد استدعاء ناجح لدالة مكتبة! يجب فحصه فقط بعد فشل استدعاء دالة.
(2) perror / strerror
void perror(const char *s);
char *strerror(int errnum);
تُخرج perror s: رسالة الخطأ إلى stderr. تُرجع strerror الوصف النصي المقابل لرمز الخطأ.
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
FILE *fp = fopen("nonexistent_file.txt", "r");
if (fp == NULL) {
perror("fopen");
printf("Error code: %d, Error message: %s\n", errno, strerror(errno));
return 1;
}
fclose(fp);
return 0;
}
fopen: No such file or directory
Error code: 2, Error message: No such file or directory
▶ مثال
نمط معالجة الأخطاء الكامل:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "Cannot open '%s': %s\n", argv[1], strerror(errno));
return 1;
}
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
if (ferror(fp)) {
fprintf(stderr, "Read error occurred: %s\n", strerror(errno));
fclose(fp);
return 1;
}
fclose(fp);
return 0;
}
ferror يتحقق مما إذا تعرض تدفق لخطأ قراءة/كتابة، وهذا يختلف عن feof (الذي يتحقق من نهاية الملف).
4. التدفقات القياسية
تفتح C ثلاثة تدفقات تلقائيًا عند بدء البرنامج:
| التدفق | الاسم | الجهاز الافتراضي | واصف الملف |
|---|---|---|---|
stdin |
الإدخال القياسي | لوحة المفاتيح | 0 |
stdout |
الإخراج القياسي | الشاشة | 1 |
stderr |
الخطأ القياسي | الشاشة | 2 |
الفرق بين stdout وstderr: stdout مُخزّن مؤقتًا، وstderr غير مُخزّن. عند إعادة التوجيه، لا يؤثر أحدهما على الآخر.
#include <stdio.h>
int main(void) {
fprintf(stdout, "This is normal output\n");
fprintf(stderr, "This is error output\n");
return 0;
}
./program > output.txt
عند التشغيل بهذه الطريقة، يذهب "الإخراج الطبيعي" إلى الملف، بينما يظل "إخراج الخطأ" ظاهرًا على الشاشة.
(1) مبادئ إعادة التوجيه
في سطر الأوامر:
>يُعيد توجيهstdoutإلى ملف2>يُعيد توجيهstderrإلى ملف<يستبدلstdinبملف|يربطstdoutلبرنامج بـstdinلبرنامج آخر
./program 2>&1 all.log
هذا الأمر يدمج stderr في stdout ويكتب كليهما في all.log.
5. الملفات المؤقتة
(1) tmpfile
FILE *tmpfile(void);
ينشئ ملفًا مؤقتًا مفتوحًا بالوضع "wb+". يُحذف الملف تلقائيًا عند إغلاقه أو انتهاء البرنامج.
#include <stdio.h>
int main(void) {
FILE *tmp = tmpfile();
if (tmp == NULL) {
perror("tmpfile");
return 1;
}
fprintf(tmp, "Temporary data %d\n", 42);
rewind(tmp);
char buf[64];
while (fgets(buf, sizeof(buf), tmp) != NULL) {
printf("%s", buf);
}
fclose(tmp);
return 0;
}
Temporary data 42
(2) tmpnam
char *tmpnam(char *s);
يُنشئ اسم ملف مؤقت لا يتعارض مع الملفات الموجودة. لكن هناك شرط سباق — بين إنشاء الاسم وإنشاء الملف، قد ينشئ برنامج آخر ملفًا بنفس الاسم.
tmpfile — فهو ينشئ الملف ويفتحه بشكل ذري، مما يجعله أكثر أمانًا. tmpnam ليس آمنًا في البيئات متعددة الخيوط.
▶ مثال
استخدام ملف مؤقت لمعالجة البيانات الوسيطة:
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *tmp = tmpfile();
if (tmp == NULL) {
perror("tmpfile");
return 1;
}
char *lines[] = {"banana", "apple", "orange", "grape"};
int n = 4;
for (int i = 0; i < n; i++) {
fprintf(tmp, "%s\n", lines[i]);
}
rewind(tmp);
printf("Before sorting:\n");
char buf[64];
while (fgets(buf, sizeof(buf), tmp) != NULL) {
buf[strcspn(buf, "\n")] = '\0';
printf(" %s\n", buf);
}
rewind(tmp);
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
char a[64], b[64];
long pos = ftell(tmp);
fgets(a, sizeof(a), tmp);
fgets(b, sizeof(b), tmp);
a[strcspn(a, "\n")] = '\0';
b[strcspn(b, "\n")] = '\0';
if (strcmp(a, b) > 0) {
fseek(tmp, pos, SEEK_SET);
fprintf(tmp, "%s\n%s\n", b, a);
} else {
fseek(tmp, pos, SEEK_SET);
fprintf(tmp, "%s\n%s\n", a, b);
}
fseek(tmp, pos + strlen(a) + strlen(b) + 2, SEEK_SET);
}
rewind(tmp);
}
rewind(tmp);
printf("After sorting:\n");
while (fgets(buf, sizeof(buf), tmp) != NULL) {
buf[strcspn(buf, "\n")] = '\0';
printf(" %s\n", buf);
}
fclose(tmp);
return 0;
}
Before sorting:
banana
apple
orange
grape
After sorting:
apple
grape
orange
banana
6. الإخراج المنسق المتقدم
(1) snprintf
int snprintf(char *str, size_t size, const char *format, ...);
مقارنة بـ sprintf، يضيف snprintf معامل size لتحديد طول الكتابة، مما يمنع تجاوز المخزن المؤقت.
#include <stdio.h>
int main(void) {
char buf[10];
int n = snprintf(buf, sizeof(buf), "Hello, %s!", "World");
printf("buf = \"%s\", required length = %d\n", buf, n);
return 0;
}
buf = "Hello, Wo", required length = 13
snprintf هي الطول الذي يجب أن يكون عليه الإخراج المنسق، وليس عدد البايتات الفعلي المكتوب. إذا كانت القيمة المُرجعة أكبر من size-1، فهذا يعني أن الإخراج قُطع.
❓ أسئلة شائعة
argv[0] هو اسم البرنامج نفسه، وargv[1] فصاعدًا هي الوسيطات التي قدمها المستخدم، بنفس الترتيب في سطر الأوامر.📖 ملخص
argc/argvيستقبلان وسيطات سطر الأوامر، حيثargv[0]هو اسم البرنامجgetenvيقرأ متغيرات البيئة، وenvironيمر على جميع متغيرات البيئةerrno+perror/strerrorهو النهج القياسي لمعالجة أخطاء المكتبة القياسيةstdoutمُخزّن مؤقتًا، وstderrغير مُخزّن، وهما مستقلان عند إعادة التوجيهtmpfileينشئ ملفًا مؤقتًا يُحذف تلقائيًا، وهو أكثر أمانًا منtmpnam
📝 تمارين
- اكتب برنامج سطر أوامر يقبل
-cلعدّ الأحرف، و-lلعدّ الأسطر، و-wلعدّ الكلمات في ملف - اكتب برنامجًا يستخدم
tmpfileلعكس محتويات ملف (اكتب كل شيء في ملف مؤقت، ثم اقرأه بالترتيب العكسي) - اكتب برنامجًا يوضح الفرق بين إعادة توجيه
stdoutوstderr، باستخدام>و2>لالتقاط كل نوع من الإخراج على حدة