SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor

حل خطأ syntax error near unexpected token في bash

يذكر bash الرمز الذي أوقفه: ( أو newline أو fi أو $'\r' أو <. هنا سبب كل رمز، ومنه الفاصلة المنقوطة العربية وتشغيل السكربت بـ sh، وطريقة إيجاد السطر المعطوب بـ bash -n.

ماذا يعني خطأ syntax error near unexpected token في bash؟

رسالة syntax error near unexpected token تعني أن bash قرأ سطراً فوجد فيه رمزاً في مكان لا تسمح به قواعد اللغة. الكلمة بين علامتي الاقتباس في آخر الرسالة هي ذلك الرمز. هذا الرمز هو أهم ما في الرسالة، ولذلك رتّبنا هذا الدليل حسبه: ( ثم newline ثم fi و done ثم $'\r' ثم <.

تأتي الرسالة عادة في سطرين:

./backup.sh: line 7: syntax error near unexpected token `('
./backup.sh: line 7: `cp تقرير (1).pdf /srv/reports/'

السطر الأول يذكر اسم الملف ورقم السطر والرمز. السطر الثاني ينسخ السطر الذي توقّف عنده bash كما قرأه. شكل علامات الاقتباس حول الرمز يختلف بين إصدارات bash وبين صفحات التوثيق، فلا تبحث بها. ابحث بالرمز نفسه.

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

اعثر على السطر المعطوب بـ bash -n و shellcheck

الخيار -n يجعل bash يقرأ الأوامر دون أن ينفّذها. دليل bash الرسمي يقول إنه يصلح لفحص السكربت بحثاً عن أخطاء الصياغة. لا يُحذف شيء ولا يُنسخ شيء.

bash -n backup.sh
echo $?

إذا لم يطبع bash -n شيئاً وطبع echo $? الرقم 0، فالصياغة سليمة. إذا طبع رسالة، فهي رسالة الصياغة التي سيطبعها التشغيل الحقيقي، مع رقم السطر.

لهذا الفحص حدّ واحد يجب أن تعرفه. -n لا ينفّذ أي أمر، ومنها shopt -s extglob داخل السكربت. لذلك قد يشتكي bash -n من نمط مثل !(*.pdf) في سكربت يعمل فعلاً. في هذه الحالة فعّل الخيار من سطر الأوامر عند الفحص:

bash -O extglob -n backup.sh

الأداة الثانية هي ShellCheck. هي أداة مجانية مرخّصة بـ GPLv3 تعطي تحذيرات واقتراحات لسكربتات bash و sh. تقرأ سطر الـ shebang (السطر الأول الذي يبدأ بـ #!) وتحذّرك من الميزات التي لا يدعمها الـ shell المذكور فيه.

sudo apt update && sudo apt install -y shellcheck
shellcheck backup.sh

كل تحذير يحمل رقم السطر ورمزاً يبدأ بـ SC. تستطيع البحث عن هذا الرمز في ويكي المشروع على GitHub لتقرأ شرحه.

الرمز (: أسماء ملفات فيها أقواس

عندما تنزّل الملف نفسه مرتين، يضيف المتصفح (1) إلى اسمه. فيصبح الاسم مثلاً تقرير (1).pdf. القوس في bash ليس حرفاً عادياً. هو رمز تحكّم يبدأ به تعريف دالة أو مصفوفة أو مجموعة أوامر تعمل في shell فرعي (subshell). عندما يجد bash القوس ( بعد كلمتين في أمر بسيط، لا يجد قاعدة تقبله، فيتوقّف ويذكره.

الحل أن تحيط الاسم بعلامات اقتباس، أو أن تسبق كل حرف خاص بشرطة مائلة عكسية:

cp "تقرير (1).pdf" /srv/reports/
cp تقرير\ \(1\).pdf /srv/reports/

السطر الثاني هو ما يكتبه bash بنفسه عندما تكتب أول حروف الاسم ثم تضغط Tab. والأفضل على الخادم أن تغيّر الاسم مرة واحدة إلى اسم بلا مسافات ولا أقواس:

mv "تقرير (1).pdf" taqrir-1.pdf

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

النمط !(…) داخل سكربت: الخيار extglob

النمط !(*.pdf) يعني كل ملف لا ينتهي بـ .pdf. هو من أنماط extglob، ودليل bash يقول إن الـ shell لا يتعرّف على هذه الأنماط إلا إذا فُعّل الخيار extglob بالأمر shopt. خذ هذا السكربت:

#!/bin/bash
shopt extglob
cd /srv/reports || exit 1
rm -- !(*.pdf)

السطر الثاني يطبع حالة الخيار داخل السكربت، كلمة extglob ثم on أو off. شغّل الأمر shopt extglob في طرفيتك أيضاً وقارن. إذا ظهرت on في الطرفية و off في السكربت، فهذا هو سبب أن الأمر يعمل حين تكتبه بيدك ويفشل داخل السكربت. عند off يصل bash إلى السطر الرابع فيرى ! كلمة عادية يتبعها (، فيطبع الخطأ ويذكر (.

الحل أن تضع shopt -s extglob في سطر مستقل قرب أعلى السكربت. لا تضعه في السطر نفسه مع النمط، مثل shopt -s extglob; rm -- !(*.pdf). هذا يفشل، لأن bash يحلّل السطر كله قبل أن ينفّذ أي جزء منه، فيرى النمط والخيار ما زال مغلقاً. ولهذا السبب نفسه لا تفعّله داخل دالة أو داخل كتلة if تستعمل النمط، لأن bash يحلّل الكتلة كاملة قبل تنفيذها. التفاصيل الكاملة للأنماط في دليل أنماط التوسيع و extglob في bash.

الرمز newline: شيء ناقص في آخر السطر

كلمة newline ليست حرفاً في سطرك. هي اسم bash لنهاية السطر. معناها أن السطر انتهى و bash ما زال ينتظر شيئاً. أشهر سبب هو عامل توجيه (redirection) مثل > أو < بلا اسم ملف بعده.

وهذا يحدث كثيراً مع الأمثلة المنسوخة من التوثيق. الكاتب يضع اسماً بين قوسين زاويين ليقول لك: ضع اسم ملفك هنا.

cat <اسم_الملف>

إذا نسخت السطر كما هو، يقرأ bash < على أنه قراءة من ملف اسمه اسم_الملف. ثم يقرأ > على أنه كتابة إلى ملف، فيبحث عن اسم الملف بعده ولا يجد إلا نهاية السطر. فيذكر newline. الحل أن تستبدل العبارة كلها مع القوسين الزاويين، مثلاً cat notes.txt. السبب نفسه وراء سطر مثل echo "test" > بلا اسم ملف.

الرموز fi و do و done: فاصل ناقص قبل الكلمة المحجوزة

الكلمات then و fi و do و done كلمات محجوزة. لكن bash يتعرّف عليها فقط في بداية أمر، أي بعد نهاية سطر أو بعد ;. في أي مكان آخر هي كلمات عادية.

if [ -f /etc/nginx/nginx.conf ] then
  echo "الملف موجود"
fi

لا توجد ; قبل then، فتصبح then مجرد معامل آخر للأمر [. يتابع bash القراءة وهو ما زال ينتظر then، فيصل إلى fi ويتوقّف ويذكرها. لاحظ أن رقم السطر في الرسالة يشير إلى سطر fi، بينما الخطأ في السطر الأول. ابحث دائماً في الأسطر التي قبل السطر المذكور.

الحلقة for لها الخطأ نفسه. في السطر for f in *.log do تصبح do عنصراً في القائمة، فيذكر bash لاحقاً done. الصيغة الصحيحة هي if [ ... ]; then و for f in *.log; do.

إذا كتبت السطر الناقص في طرفية تفاعلية، قد لا ترى خطأ فوراً. ترى بدلاً منه علامة > في بداية سطر جديد، لأن bash ينتظر بقية الكتلة. وإذا نقصت fi أو done تماماً من سكربت، تكون الرسالة syntax error: unexpected end of file، والسبب من العائلة نفسها.

الفاصلة المنقوطة العربية وعلامات الاتجاه الخفية

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

  • الفاصلة المنقوطة العربية ؛، واسمها في يونيكود ARABIC SEMICOLON (U+061B).
  • الفاصلة العربية ،، واسمها ARABIC COMMA (U+060C).
  • علامة LEFT-TO-RIGHT MARK (U+200E) وعلامة RIGHT-TO-LEFT MARK (U+200F). هاتان من فئة حروف التنسيق في يونيكود، فلا تظهران على الشاشة أبداً.

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

bash لا يعرف إلا الفاصلة المنقوطة ; من جدول ASCII. أما ؛ فهي عنده حرف مثل أي حرف آخر، فتلتصق بالكلمة التي قبلها.

if [ -f /etc/hosts ]؛ then
  echo "موجود"
fi

هنا ]؛ كلمة واحدة، و then ليست في بداية أمر. فالنتيجة هي الخطأ نفسه في القسم السابق: يذكر bash fi. الفرق أن السطر يبدو صحيحاً تماماً لعينك.

الفاصلة العربية ، لا تسبّب خطأ صياغة في الغالب، لكنها تعطيك نتيجة خاطئة بصمت. الأمر mkdir -p logs/{2025،2026} يصنع مجلداً واحداً اسمه حرفياً {2025،2026}، لأن توسيع الأقواس المعقوفة يحتاج الفاصلة , من ASCII.

علامات الاتجاه أصعب، لأنك لا تراها. إذا التصقت بكلمة then أو done فهذه الكلمة لم تعد كلمة محجوزة، فيظهر خطأ قرب fi أو done في سطر يبدو سليماً. وإذا التصقت باسم أمر، يظهر command not found لأمر تعرف أنه مثبّت.

ثلاثة أوامر تكشف هذه الحروف:

grep -nP '[^\x00-\x7F]' script.sh
cat -A script.sh
od -c script.sh | head -n 20

الأمر grep يطبع رقم كل سطر فيه حرف خارج ASCII. النص العربي داخل echo سيظهر هنا أيضاً، وهذا طبيعي. ركّز على الأسطر التي لا ترى فيها حرفاً عربياً. الأمر cat -A يعرض كل بايت خارج ASCII بصيغة تبدأ بـ M-، فتظهر علامة RIGHT-TO-LEFT MARK مثلاً كسلسلة M-bM-^@M-^O في مكان تراه فارغاً في المحرّر. الأمر od -c يعرض قيم البايتات بالنظام الثماني، فترى أرقاماً مثل 330 233 حيث كنت تتوقع ;.

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

cp script.sh script.sh.bak
LC_ALL=C sed -i 's/\xe2\x80\x8e//g; s/\xe2\x80\x8f//g' script.sh
grep -nP '[^\x00-\x7F]' script.sh

LC_ALL=C يجعل sed يطابق البايتات كما هي. بعد الحذف، يجب ألا يطبع grep إلا الأسطر التي فيها نص عربي تعرفه.

الرمز $'\r': سكربت حُفظ على Windows

Windows ينهي كل سطر بحرفين: CR ثم LF (أي carriage return ثم line feed). Linux ينتظر LF وحده. bash يعامل حرف CR الزائد كحرف عادي، فيلتصق بآخر كلمة في كل سطر. عندها تصبح { هي { يتبعها CR، وتصبح then كلمة أخرى غير then. ولأن CR لا يُرى، يكتبه bash في الرسالة بصيغة $'\r'، وأحياناً ملتصقاً بالكلمة، مثل $'{\r'.

الأسطر الفارغة في ملف كهذا تعطي رسالة ثانية مشهورة، $'\r': command not found، لأن السطر الفارغ صار يحتوي على حرف CR وحده، و bash يحاول تشغيله كأمر.

اكشف المشكلة بالأمر cat -A:

cat -A greet.sh

في ملف سليم ينتهي كل سطر بـ $. في ملف من Windows ينتهي كل سطر بـ ^M$، و ^M هو حرف CR. ShellCheck يعرف هذه الحالة أيضاً، وعنوان صفحة SC1017 في الويكي هو Literal carriage return. Run script through tr -d '\r'. احذف الحرف بأحد الأمرين:

sed -i 's/\r$//' greet.sh
sudo apt install -y dos2unix && dos2unix greet.sh
cat -A greet.sh

بعد الإصلاح يجب أن ينتهي كل سطر بـ $ فقط. ولكي لا تعود المشكلة، اضبط محرّرك على Windows ليحفظ الملفات بنهايات LF.

تشغيل السكربت بـ sh بدل bash

الأمر sh script.sh يتجاهل سطر الـ shebang، ويشغّل الملف بالبرنامج الذي يشير إليه /bin/sh. هذا البرنامج ليس bash دائماً. اعرف ما هو على نظامك:

readlink -f /bin/sh

إذا طبع /usr/bin/dash، فالملف يعمل تحت dash. صفحة دليل dash في Debian تصفه بأنه تطبيق متوافق مع POSIX لـ /bin/sh يهدف إلى أن يكون صغيراً قدر الإمكان. هذا يعني أن ميزات bash الخاصة، مثل المصفوفات و [[ و !(…)، ليست جزءاً منه.

#!/bin/bash
servers=(web1 web2 db1)
echo "${servers[0]}"

الأمر bash deploy.sh يطبع web1. أما sh deploy.sh على نظام يشير فيه /bin/sh إلى dash فيفشل عند السطر الثاني برسالة شكلها مختلف، مثل deploy.sh: 2: Syntax error: "(" unexpected. انظر إلى الشكل: حرف S كبير، ولا توجد كلمة line، ولا توجد عبارة near unexpected token. هذا الشكل يقول لك إن من قرأ الملف هو dash وليس bash. وإذا ظهرت [[: not found فالسبب نفسه.

الحل أن تشغّل الملف بـ bash deploy.sh، أو أن تجعله قابلاً للتنفيذ بـ chmod +x deploy.sh ثم تشغّله بـ ./deploy.sh حتى يُحترم سطر الـ shebang. الفرق بين هذه الطرق وبين source مشروح في الفرق بين تنفيذ سكربت bash وتحميله بـ source.

هذا أيضاً سبب شائع لفشل أسطر crontab. صفحة crontab(5) تقول إن الأمر يُنفَّذ بـ /bin/sh أو بالـ shell المذكور في المتغير SHELL داخل ملف crontab. فالسطر الذي يستعمل ميزة من bash مباشرة في crontab يُقرأ بـ /bin/sh. ضع SHELL=/bin/bash في أعلى ملف crontab، أو استدعِ السكربت بـ bash /opt/scripts/backup.sh. تفاصيل ملفات cron تختلف بين تطبيقاتها، فاقرأ man 5 crontab على نظامك. وبقية أسباب فشل المهام المجدولة في لماذا لا تعمل مهمة cron.

الرمز <: مثبّت Claude Code أعاد صفحة ويب بدل السكربت

توثيق Claude Code يثبّته على Linux و macOS بهذا الأمر:

curl -fsSL https://claude.ai/install.sh | bash

curl ينزّل الملف، و bash ينفّذ ما يصله فوراً. إذا أعاد الخادم صفحة HTML بدل السكربت، يحاول bash تنفيذ HTML. صفحة استكشاف أخطاء التثبيت في توثيق Claude Code تقول إن الخطأ في Bash أو Zsh يقتبس أول سطر من الصفحة المعادة.

تستطيع أن ترى هذا بنفسك دون أي اتصال بالشبكة. اصنع صفحتين محليتين وأرسل كلاً منهما إلى bash:

printf '<!DOCTYPE html>\n<html><body>App unavailable in region</body></html>\n' > page-lines.html
printf '<!DOCTYPE html><html><body>App unavailable in region</body></html>\n' > page-oneline.html
cat page-lines.html | bash
cat page-oneline.html | bash

الرمز الذي يذكره bash يختلف بين الملفين، والسبب في قواعد التوجيه. السطر <!DOCTYPE html> يبدأ بـ <، فيقرؤه bash قراءةً من ملف اسمه !DOCTYPE. ثم html اسم أمر. ثم > توجيه يحتاج اسم ملف بعده. في الملف الأول ينتهي السطر بعد >، فيذكر bash newline. في الملف الثاني يأتي بعد > مباشرة <html>، فيذكر bash <، وهو ما تعرضه صفحة التوثيق. في الحالتين يتوقّف bash عند السطر الأول، فلا يُنفَّذ شيء من الصفحة.

العلامة التي تهمّك هي السطر الثاني من رسالة الخطأ. إذا بدأ بـ <!DOCTYPE أو <html، فما نزل ليس سكربتاً. لترى ما يعيده الخادم لك فعلاً، نزّل الملف أولاً بالأمر curl -fsSL https://claude.ai/install.sh -o install.sh، ثم اقرأ أوله بالأمر head -n 5 install.sh قبل أن تشغّل أي شيء.

ماذا تعني الصفحة، وما الحلول الموثّقة؟

التوثيق يقول إن هذه الأخطاء كلها تعني أن رابط التثبيت أعاد صفحة ويب أو مستند XML أو حالة خطأ بدل السكربت. وإذا اقتبست رسالة الخطأ عبارة App unavailable in region، فـ Claude Code غير متاح في بلدك. معنى ذلك ببساطة: Claude Code غير مقدَّم من المكان الذي خرج منه الطلب. هذا قرار من Anthropic بشأن البلدان التي تقدّم فيها خدماتها، وليس عطلاً في نظامك. وهذا الدليل لا يقدّم شبكة VPN أو خادماً في بلد آخر طريقاً للالتفاف عليه.

صفحة Supported Regions Policy عند Anthropic تقول إن منتجاتها، ومنها Claude Code، متاحة فقط في البلدان والمناطق المذكورة فيها. عندما قرأنا القائمة في 2026-10-18 لم تكن سوريا ولا اليمن فيها. القائمة تتغيّر، فلا تعتمد على نسخة محفوظة منها. راجع أين يتوفر Claude في كل بلد قبل أن تبدأ.

إذا لم تظهر عبارة المنطقة، فالتوثيق يذكر أسباباً أخرى: مشكلات في الشبكة، أو التوجيه الإقليمي، أو تعطّل مؤقت في الخدمة. وقد ترى curl: (22) The requested URL returned error: 403 بلا أي صفحة. التوثيق يقول إن هذا غالباً له السبب نفسه، لكنه قد يأتي أيضاً من خادم وسيط (proxy) أو جدار حماية في شبكة شركتك يمنع التنزيل.

الحلول الموثّقة اثنان:

  1. أعد المحاولة بعد بضع دقائق. التوثيق يقول إن المشكلة مؤقتة في الغالب.
  2. استعمل طريقة تثبيت بديلة: brew install --cask claude-code على macOS، أو winget install Anthropic.ClaudeCode على Windows. التثبيت بـ Homebrew أو WinGet لا يحدّث نفسه تلقائياً بشكل افتراضي، على عكس التثبيت الأصلي.

هذا القسم من التوثيق لا يذكر بديلاً لـ Linux. وهو يقول أيضاً إن الطرق البديلة تصل إلى الخوادم نفسها. فإذا كنت في بلد مدعوم وما زلت ترى 403، افحص اتصالك بالشبكة أولاً. وإذا كان الخطأ عن المنطقة، فلن تغيّر طريقة التثبيت شيئاً. بعد أن ينجح التثبيت، تجد طرق تشغيله على الخادم وسطح المكتب في Claude على Linux في سطح المكتب وسطر الأوامر.

FAQ

لماذا يعمل الأمر في الطرفية ويفشل بخطأ syntax error داخل السكربت؟

هناك ثلاثة أسباب شائعة. الأول أن الخيار extglob مفعّل في طرفيتك ومغلق داخل السكربت، فيفشل نمط مثل !(*.pdf) عند (. قارن ناتج shopt extglob في المكانين، وضع shopt -s extglob في سطر مستقل في أعلى السكربت. الثاني أنك تشغّل الملف بـ sh أو من cron، فيقرؤه /bin/sh، وقد يكون dash. اعرف ذلك بـ readlink -f /bin/sh. الثالث أن الملف حُفظ على Windows بنهايات CRLF، وهذا يظهر في cat -A كـ ^M$ في آخر كل سطر.

ماذا تعني كلمة newline في رسالة syntax error near unexpected token؟

تعني أن السطر انتهى و bash ما زال ينتظر شيئاً. أشهر سبب هو > أو < بلا اسم ملف بعده. ويحدث هذا كثيراً عند نسخ مثال من التوثيق فيه اسم بين قوسين زاويين، مثل cat <اسم_الملف>. استبدل العبارة كلها، مع القوسين، باسم ملفك الحقيقي.

كيف أعرف إن كان في سطري حرف عربي مخفي يكسر الأمر؟

شغّل grep -nP '[^\x00-\x7F]' script.sh ليطبع كل سطر فيه حرف خارج ASCII، ثم انظر إلى ذلك السطر بـ cat -A. الفاصلة المنقوطة العربية ؛ وعلامات الاتجاه U+200E و U+200F تظهر هناك كسلاسل تبدأ بـ M-. bash لا يعرف إلا ; من ASCII، فالفاصلة العربية تلتصق بالكلمة التي قبلها، وعلامة الاتجاه الملتصقة بـ then أو done تجعلها كلمة عادية. أعد كتابة السطر ولوحة المفاتيح على الإنجليزية.

لماذا يطبع مثبّت Claude Code الخطأ syntax error near unexpected token مع الرمز <؟

لأن الرابط أعاد صفحة HTML بدل سكربت التثبيت، و bash حاول تنفيذها. أول سطر في الصفحة يبدأ بـ <!DOCTYPE، وهذا ما تقتبسه الرسالة. إذا ظهرت عبارة App unavailable in region، فـ Claude Code غير متاح من المكان الذي خرج منه الطلب، وتجد البلدان المدعومة في صفحة Anthropic الرسمية. غير ذلك، يذكر التوثيق مشكلات الشبكة والتوجيه الإقليمي والتعطّل المؤقت، وحلوله هي إعادة المحاولة بعد دقائق، أو Homebrew على macOS، أو WinGet على Windows.