يجعل Bun Shell كتابة نصوص shell مع JavaScript و TypeScript ممتعة. إنها shell متعددة المنصات تشبه bash مع توافق سلس مع JavaScript.
البدء السريع:
import { $ } from "bun";
const response = await fetch("https://example.com");
// استخدم Response كـ stdin.
await $`cat < ${response} | wc -c`; // 1256الميزات
- متعددة المنصات: تعمل على Windows و Linux و macOS. بدلاً من
rimrafأوcross-env، يمكنك استخدام Bun Shell بدون تثبيت تبعيات إضافية. يتم تنفيذ أوامر shell الشائعة مثلlsوcdوrmبشكل أصلي. - مألوفة: Bun Shell هي shell تشبه bash، تدعم إعادة التوجيه والأنابيب ومتغيرات البيئة والمزيد.
- Globs: أنماط Glob مدعومة أصليًا، بما في ذلك
**و*و{expansion}والمزيد. - قوالب النصوص: تُستخدم قوالب النصوص لتنفيذ أوامر shell. هذا يسمح بالاستيفاء السهل للمتغيرات والتعابير.
- الأمان: يهرب Bun Shell جميع السلاسل افتراضيًا، مما يمنع هجمات حقن shell.
- توافق JavaScript: استخدم
ResponseوArrayBufferوBlobوBun.file(path)وكائنات JavaScript الأخرى كـ stdin و stdout و stderr. - نصوص shell: يمكن استخدام Bun Shell لتشغيل نصوص shell (ملفات
.bun.sh). - مترجم مخصص: كُتب Bun Shell في Zig، مع lexer و parser و interpreter. Bun Shell هي لغة برمجة صغيرة.
البدء
أبسط أمر shell هو echo. لتشغيله، استخدم وسم قالب النص $:
import { $ } from "bun";
await $`echo "Hello World!"`; // Hello World!افتراضيًا، تطبع أوامر shell إلى stdout. لكتم الإخراج، استدعِ .quiet():
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // لا إخراجماذا لو أردت الوصول إلى إخراج الأمر كنص؟ استخدم .text():
import { $ } from "bun";
// .text() يستدعي .quiet() تلقائيًا لك
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\nافتراضيًا، سيعيد await stdout و stderr كـ Buffers.
import { $ } from "bun";
const { stdout, stderr } = await $`echo "Hello!"`.quiet();
console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
console.log(stderr); // Buffer(0) []معالجة الأخطاء
افتراضيًا، رموز الخروج غير الصفرية ستطرح خطأ. يحتوي ShellError هذا على معلومات حول أمر التشغيل.
import { $ } from "bun";
try {
const output = await $`something-that-may-fail`.text();
console.log(output);
} catch (err) {
console.log(`فشل برمز خروج ${err.exitCode}`);
console.log(err.stdout.toString());
console.log(err.stderr.toString());
}يمكن تعطيل الطرح باستخدام .nothrow(). سيكون من الضروري التحقق من exitCode للنتيجة يدويًا.
import { $ } from "bun";
const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();
if (exitCode !== 0) {
console.log(`رمز خروج غير صفري ${exitCode}`);
}
console.log(stdout);
console.log(stderr);يمكن تكوين المعالجة الافتراضية لرموز الخروج غير الصفرية عن طريق استدعاء .nothrow() أو .throws(boolean) على دالة $ نفسها.
import { $ } from "bun";
// وعود shell لن تطرح، مما يعني أنه سيتعين عليك
// التحقق من `exitCode` يدويًا في كل أمر shell.
$.nothrow(); // يعادل $.throws(false)
// السلوك الافتراضي، رموز الخروج غير الصفرية ستطرح خطأ
$.throws(true);
// اسم مستعار لـ $.nothrow()
$.throws(false);
await $`something-that-may-fail`; // لا يتم طرح استثناءإعادة التوجيه
يمكن إعادة توجيه إدخال أو إخراج الأمر باستخدام مشغلي Bash المعتادين:
<إعادة توجيه stdin>أو1>إعادة توجيه stdout2>إعادة توجيه stderr&>إعادة توجيه كل من stdout و stderr>>أو1>>إعادة توجيه stdout، إضافة إلى الوجهة، بدلاً من الكتابة فوقه2>>إعادة توجيه stderr، إضافة إلى الوجهة، بدلاً من الكتابة فوقه&>>إعادة توجيه كل من stdout و stderr، إضافة إلى الوجهة، بدلاً من الكتابة فوقه1>&2إعادة توجيه stdout إلى stderr (جميع الكتابات إلى stdout ستكون بدلاً من ذلك في stderr)2>&1إعادة توجيه stderr إلى stdout (جميع الكتابات إلى stderr ستكون بدلاً من ذلك في stdout)
يدعم Bun Shell أيضًا إعادة التوجيه من وإلى كائنات JavaScript.
مثال: إعادة توجيه الإخراج إلى كائنات JavaScript (>)
لإعادة توجيه stdout إلى كائن JavaScript، استخدم المشغل >:
import { $ } from "bun";
const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;
console.log(buffer.toString()); // Hello World!\nالكائنات JavaScript التالية مدعومة لإعادة التوجيه إليها:
BufferوUint8ArrayوUint16ArrayوUint32ArrayوInt8ArrayوInt16ArrayوInt32ArrayوFloat32ArrayوFloat64ArrayوArrayBufferوSharedArrayBuffer(يكتب إلى المخزن المؤقت الأساسي)Bun.file(path)وBun.file(fd)(يكتب إلى الملف)
مثال: إعادة توجيه الإدخال من كائنات JavaScript (<)
لإعادة توجيه الإخراج من كائنات JavaScript إلى stdin، استخدم المشغل <:
import { $ } from "bun";
const response = new Response("hello i am a response body");
const result = await $`cat < ${response}`.text();
console.log(result); // hello i am a response bodyالكائنات JavaScript التالية مدعومة لإعادة التوجيه منها:
BufferوUint8ArrayوUint16ArrayوUint32ArrayوInt8ArrayوInt16ArrayوInt32ArrayوFloat32ArrayوFloat64ArrayوArrayBufferوSharedArrayBuffer(يقرأ من المخزن المؤقت الأساسي)Bun.file(path)وBun.file(fd)(يقرأ من الملف)Response(يقرأ من الجسم)
مثال: إعادة توجيه stdin -> ملف
import { $ } from "bun";
await $`cat < myfile.txt`;مثال: إعادة توجيه stdout -> ملف
import { $ } from "bun";
await $`echo bun! > greeting.txt`;مثال: إعادة توجيه stderr -> ملف
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;مثال: إعادة توجيه stderr -> stdout
import { $ } from "bun";
// يعيد توجيه stderr إلى stdout، لذا سيكون كل الإخراج
// متاحًا على stdout
await $`bun run ./index.ts 2>&1`;مثال: إعادة توجيه stdout -> stderr
import { $ } from "bun";
// يعيد توجيه stdout إلى stderr، لذا سيكون كل الإخراج
// متاحًا على stderr
await $`bun run ./index.ts 1>&2`;الأنابيب (|)
كما في bash، يمكنك توجيه إخراج أمر إلى آخر:
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\nيمكنك أيضًا التوجيه مع كائنات JavaScript:
import { $ } from "bun";
const response = new Response("hello i am a response body");
const result = await $`cat < ${response} | wc -w`.text();
console.log(result); // 6\nاستبدال الأمر ($(...))
يسمح استبدال الأمر باستبدال إخراج نص آخر في النص الحالي:
import { $ } from "bun";
// يطبع hash للـ commit الحالي
await $`echo Hash of current commit: $(git rev-parse HEAD)`;هذا إدراج نصي لإخراج الأمر ويمكن استخدامه، على سبيل المثال، للإعلان عن متغير shell:
import { $ } from "bun";
await $`
REV=$(git rev-parse HEAD)
docker built -t myapp:$REV
echo Done building docker image "myapp:$REV"
`;NOTE
لأن Bun يستخدم داخليًا الخاصية الخاصة raw على قالب النص المدخل، لن يعمل استخدام صيغة backtick لاستبدال الأمر:
import { $ } from "bun";
await $`echo \`echo hi\``;بدلاً من الطباعة:
hiالأعلى ستطبع:
echo hiنوصي بدلاً من ذلك بالالتزام بصيغة $(...).
متغيرات البيئة
يمكن تعيين متغيرات البيئة كما في bash:
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\nيمكنك استخدام استيفاء السلسلة لتعيين متغيرات البيئة:
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\nيتم هروب الإدخال افتراضيًا، مما يمنع هجمات حقن shell:
import { $ } from "bun";
const foo = "bar123; rm -rf /tmp";
await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\nتغيير متغيرات البيئة
افتراضيًا، يُستخدم process.env كمتغيرات بيئة لجميع الأوامر.
يمكنك تغيير متغيرات البيئة لأمر واحد عن طريق استدعاء .env():
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // barيمكنك تغيير متغيرات البيئة الافتراضية لجميع الأوامر عن طريق استدعاء $.env:
import { $ } from "bun";
$.env({ FOO: "bar" });
// $FOO المحدد عالميًا
await $`echo $FOO`; // bar
// $FOO المحدد محليًا
await $`echo $FOO`.env({ FOO: "baz" }); // bazيمكنك إعادة تعيين متغيرات البيئة إلى الافتراضي عن طريق استدعاء $.env() بدون وسيطات:
import { $ } from "bun";
$.env({ FOO: "bar" });
// $FOO المحدد عالميًا
await $`echo $FOO`; // bar
// $FOO المحدد محليًا
await $`echo $FOO`.env(undefined); // ""تغيير دليل العمل
يمكنك تغيير دليل العمل لأمر عن طريق تمرير سلسلة إلى .cwd():
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmpيمكنك تغيير دليل العمل الافتراضي لجميع الأوامر عن طريق استدعاء $.cwd:
import { $ } from "bun";
$.cwd("/tmp");
// دليل العمل المحدد عالميًا
await $`pwd`; // /tmp
// دليل العمل المحدد محليًا
await $`pwd`.cwd("/"); // /قراءة الإخراج
لقراءة إخراج أمر كسلسلة، استخدم .text():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\nقراءة الإخراج كـ JSON
لقراءة إخراج أمر كـ JSON، استخدم .json():
import { $ } from "bun";
const result = await $`echo '{"foo": "bar"}'`.json();
console.log(result); // { foo: "bar" }قراءة الإخراج سطرًا بسطر
لقراءة إخراج أمر سطرًا بسطر، استخدم .lines():
import { $ } from "bun";
for await (let line of $`echo "Hello World!"`.lines()) {
console.log(line); // Hello World!
}يمكنك أيضًا استخدام .lines() على أمر مكتمل:
import { $ } from "bun";
const search = "bun";
for await (let line of $`cat list.txt | grep ${search}`.lines()) {
console.log(line);
}قراءة الإخراج كـ Blob
لقراءة إخراج أمر كـ Blob، استخدم .blob():
import { $ } from "bun";
const result = await $`echo "Hello World!"`.blob();
console.log(result); // Blob(13) { size: 13, type: "text/plain" }الأوامر المدمجة
لتوافق المنصات، يطبق Bun Shell مجموعة من الأوامر المدمجة، بالإضافة إلى قراءة الأوامر من متغير البيئة PATH.
cd: تغيير دليل العملls: سرد الملفات في دليلrm: إزالة الملفات والمجلداتecho: طباعة نصpwd: طباعة دليل العملbun: تشغيل bun في buncattouchmkdirwhichmvexittruefalseyesseqdirnamebasename
مُطبق جزئيًا:
mv: نقل الملفات والمجلدات (بدون دعم عبر الأجهزة)
غير مُطبق بعد، لكن مخطط له:
- راجع Issue #9716 للحصول على القائمة الكاملة.
الأدوات
يطبق Bun Shell أيضًا مجموعة من الأدوات للعمل مع shells.
$.braces (توسيع الأقواس)
تطبق هذه الدالة توسيع الأقواس البسيط لأوامر shell:
import { $ } from "bun";
await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]$.escape (هروب السلاسل)
تعرض منطق هروب Bun Shell كدالة:
import { $ } from "bun";
console.log($.escape('$(foo) `bar` "baz"'));
// => \$(foo) \`bar\` \"baz\"إذا لم ترد هروب السلسلة الخاصة بك، فلفها في كائن { raw: 'str' }:
import { $ } from "bun";
await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => bazمحمل ملف .sh
للنصوص shell البسيطة، بدلاً من /bin/sh، يمكنك استخدام Bun Shell لتشغيل نصوص shell.
للقيام بذلك، فقط شغل النص مع bun على ملف بامتداد .sh.
echo "Hello World! pwd=$(pwd)"bun ./script.shHello World! pwd=/home/demoالنصوص مع Bun Shell متعددة المنصات، مما يعني أنها تعمل على Windows:
bun .\script.shHello World! pwd=C:\Users\Demoملاحظات التطبيق
Bun Shell هي لغة برمجة صغيرة في Bun مطبقة في Zig. تتضمن lexer و parser و interpreter مكتوبة يدويًا. على عكس bash و zsh و shells الأخرى، يعمل Bun Shell العمليات بشكل متزامن.
الأمان في Bun shell
بحكم التصميم، لا يستدعي Bun shell shell نظام (مثل /bin/sh) وبدلاً من ذلك هو إعادة تطبيق لـ bash يعمل في نفس عملية Bun، مصمم مع وضع الأمان في الاعتبار.
عند تحليل وسيطات الأمر، يعامل جميع المتغيرات المستوفاة كسلاسل واحدة حرفية.
هذا يحمي Bun shell من حقن الأمر:
import { $ } from "bun";
const userInput = "my-file.txt; rm -rf /";
// آمن: يُعامل `userInput` كسلسلة مفردة مقتبسة
await $`ls ${userInput}`;في المثال أعلاه، يُعامل userInput كسلسلة واحدة. هذا يتسبب في محاولة أمر ls قراءة محتويات دليل واحد باسم "my-file; rm -rf /".
اعتبارات الأمان
على الرغم من منع حقن الأمر افتراضيًا، لا يزال المطورون مسؤولين عن الأمان في سيناريوهات معينة.
على غرار واجهات Bun.spawn أو node:child_process.exec()، يمكنك عمدًا تنفيذ أمر يولد shell جديد (مثل bash -c) مع وسيطات.
عندما تفعل ذلك، تسلم التحكم، ولم تعد حماية Bun المدمجة تنطبق على السلسلة التي يفسرها shell الجديد.
import { $ } from "bun";
const userInput = "world; touch /tmp/pwned";
// غير آمن: لقد بدأت صراحة عملية shell جديدة مع `bash -c`.
// سيعمل هذا shell الجديد أمر `touch`. أي إدخال مستخدم
// يمرر بهذه الطريقة يجب أن يُنظف بدقة.
await $`bash -c "echo ${userInput}"`;حقن الوسيطات
لا يمكن لـ Bun shell معرفة كيف يفسر أمر خارجي وسيطات سطر الأوامر الخاصة به. يمكن للمهاجم تقديم إدخال يتعرف عليه البرنامج المستهدف كأحد خياراته أو أعلامه الخاصة، مما يؤدي إلى سلوك غير مقصود.
import { $ } from "bun";
// إدخال ضار منسق كأمر Git لسطر الأوامر
const branch = "--upload-pack=echo pwned";
// غير آمن: بينما يمرر Bun السلسلة بأمان كوسيطة واحدة،
// يرى برنامج `git` نفسه العلم الضار ويتصرف بناءً عليه.
await $`git ls-remote origin ${branch}`;NOTE
**التوصية** - كما هو أفضل ممارسة في كل لغة، قم دائمًا بتنظيف الإدخال المقدم من المستخدم قبل تمريره كوسيطة لأمر خارجي. تقع مسؤولية التحقق من صحة الوسيطات على كود التطبيق الخاص بك.الشكر
أجزاء كبيرة من هذه الواجهة مستوحاة من zx و dax و bnx. شكرًا لمؤلفي تلك المشاريع.