Hôm nay, anh Creyt sẽ dẫn mấy đứa đi "săn" một con quái vật khá "ngầu" trong rừng Node.js, tên là child_process.exec(). Nghe tên thôi đã thấy "deep" rồi đúng không? Đừng lo, anh sẽ "phù phép" cho nó dễ hiểu như ăn kẹo!
1. child_process.exec() là gì mà "flex" thế?
Tưởng tượng Node.js của mấy đứa là một "đại bản doanh" siêu xịn sò, đang làm đủ thứ việc server, API các kiểu con đà điểu. Nhưng đôi khi, mấy đứa cần một "thằng đệ" chạy ra ngoài, làm mấy cái việc lặt vặt mà Node.js không "rảnh" làm trực tiếp, ví dụ như chạy một lệnh terminal, một script vỏ bọc (shell script), hay thậm chí là một chương trình độc lập nào đó. child_process.exec() chính là "thằng đệ" đó!
Nó làm gì? Đơn giản là nó sẽ thực thi một lệnh nào đó trong một shell (như Bash trên Linux/macOS hay Command Prompt trên Windows) và sau đó, nó sẽ thu thập toàn bộ output (kết quả và lỗi) của lệnh đó vào một bộ đệm (buffer). Xong xuôi, nó mang tất cả về cho Node.js xử lý.
Để làm gì? Để Node.js của mấy đứa có thể "ra lệnh" cho hệ điều hành làm những việc mà bản thân Node.js không có sẵn hàm để làm. Ví dụ, đọc thông tin hệ thống, chạy các công cụ dòng lệnh khác (như git, ffmpeg, imagemagick), hoặc thậm chí là chạy các script được viết bằng ngôn ngữ khác (Python, Ruby...). Nghe "quyền năng" phết đúng không?
2. Code Ví Dụ Minh Hoạ: "Thằng đệ" bắt đầu làm việc!
Để dùng exec(), mấy đứa cần import module child_process của Node.js. Cú pháp cơ bản của nó trông như thế này:
const { exec } = require('child_process');
// Ví dụ 1: Lấy danh sách file và thư mục (như lệnh 'ls -l' trên Linux/macOS hoặc 'dir' trên Windows)
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`Lỗi rồi mấy đứa ơi: ${error.message}`);
return;
}
if (stderr) {
console.error(`Lỗi "than phiền" từ thằng đệ: ${stderr}`);
return;
}
console.log(`Kết quả "báo cáo" của thằng đệ:
${stdout}`);
});
// Ví dụ 2: Chạy một script Python đơn giản và truyền tham số
// Giả sử mấy đứa có một file `myscript.py` với nội dung:
// import sys
// print(f"Hello from Python, {sys.argv[1]}!")
// print(f"This is a test run at {sys.argv[2]}")
const username = 'Creyt';
const timestamp = new Date().toISOString();
exec(`python myscript.py ${username} ${timestamp}`, (error, stdout, stderr) => {
if (error) {
console.error(`Lỗi khi chạy Python: ${error.message}`);
return;
}
if (stderr) {
console.error(`Python "than phiền": ${stderr}`);
return;
}
console.log(`Python "báo cáo" xong:
${stdout}`);
});
// Ví dụ 3: Xử lý lỗi khi lệnh không tồn tại
exec('nonexistent_command', (error, stdout, stderr) => {
if (error) {
console.error(`Thằng đệ không tìm thấy lệnh: ${error.message}`);
// Output sẽ giống như: `Error: Command failed: nonexistent_command`
// hoặc `Error: spawn nonexistent_command ENOENT`
return;
}
console.log(`Output: ${stdout}`);
console.error(`Stderr: ${stderr}`);
});
Giải thích sơ bộ:
exec(command, callback):commandlà chuỗi lệnh mà mấy đứa muốn chạy.callbacklà hàm sẽ được gọi khi lệnh chạy xong (hoặc bị lỗi).callback(error, stdout, stderr): Hàm callback này nhận 3 tham số:error: Nếu có lỗi xảy ra khi chạy lệnh (ví dụ, lệnh không tồn tại, hoặc lệnh trả về mã lỗi khác 0).stdout: Toàn bộ dữ liệu mà lệnh in ra console thành công.stderr: Toàn bộ dữ liệu mà lệnh in ra console khi có lỗi hoặc cảnh báo.
3. Mẹo (Best Practices) để ghi nhớ và dùng "chuẩn bài"
-
Security là "chân ái": Đừng bao giờ, ANH NHẮC LẠI, ĐỪNG BAO GIỜ, chạy
exec()với input trực tiếp từ người dùng mà không qua khâu "kiểm duyệt an ninh" gắt gao. Nó là cổng hậu để hacker "flex" đấy! Tưởng tượng mấy đứa cho phép người dùng nhập vàorm -rf /thì thôi rồi, "toang" cả server. Luôn luôn sanitize và validate mọi input trước khi cho "thằng đệ" chạy lệnh. Hoặc tốt nhất, dùngexecFile()nếu mấy đứa chỉ muốn chạy một file thực thi cụ thể mà không cần qua shell. -
Output "cỡ nhỏ" thôi:
exec()sẽ lưu tất cả output vào bộ đệm trong RAM. Nếu lệnh của mấy đứa tạo ra hàng GB dữ liệu, thì server của mấy đứa sẽ "đột quỵ" vì tràn RAM.exec()phù hợp cho các lệnh ngắn, output ít. Nếu cần xử lý output "dài hơi" kiểu livestream, hãy nghĩ đếnchild_process.spawn().
-
Bất đồng bộ (Async) là "key":
exec()chạy bất đồng bộ, nghĩa là Node.js vẫn tiếp tục làm việc khác trong khi "thằng đệ" đang chạy lệnh. Đừng nhầm lẫn là nó "block" Node.js nhé. Tuy nhiên, bản thân lệnh mà "thằng đệ" chạy có thể là blocking đối với nó. -
Luôn luôn xử lý
errorvàstderr: Đừng bao giờ bỏ qua 2 cái này. Nó chính là "bộ đàm" để mấy đứa biết "thằng đệ" có gặp trục trặc gì không.
4. Ứng dụng thực tế: Ai đã dùng exec()?
"Thằng đệ" exec() này được dùng nhiều hơn mấy đứa nghĩ đấy:
- Hệ thống Build/Deploy tự động: Chạy các lệnh như
npm run build,git pull,pm2 reloadđể tự động hóa quá trình triển khai ứng dụng. - Xử lý Media: Gọi các công cụ mạnh mẽ như
ffmpegđể chuyển đổi video,ImageMagickđể xử lý ảnh (resize, watermark) trên server. - Tương tác với các công cụ CLI khác: Ví dụ, một CMS có thể dùng
exec()để gọipandocchuyển đổi định dạng tài liệu, hoặcaws-cliđể tương tác với dịch vụ AWS. - Lấy thông tin hệ thống: Chạy các lệnh như
df -h(kiểm tra dung lượng đĩa),uptime(thời gian hoạt động của server).
5. Thử nghiệm và Nên dùng cho Case nào?
Anh Creyt đã từng thử nghiệm với exec() rất nhiều: Từ việc tự động nén ảnh khi upload, chuyển đổi định dạng file, đến việc tự động cập nhật code trên server. Nó cực kỳ tiện lợi cho các tác vụ "nhỏ mà có võ".
Nên dùng exec() khi:
- Lệnh ngắn, output nhỏ: Mấy đứa chỉ cần chạy một lệnh đơn giản và lấy kết quả một lần. Ví dụ:
git rev-parse HEADđể lấy commit hash hiện tại, hoặccat /proc/cpuinfođể lấy thông tin CPU. - Không cần tương tác: Lệnh chỉ cần chạy một lần và trả về kết quả, không cần Node.js "chat" qua lại với nó.
- Cần môi trường Shell: Mấy đứa muốn tận dụng các tính năng của shell như pipe (
|), redirect (>), hoặc các biến môi trường của shell.
Không nên dùng exec() (và nên xem xét spawn() hoặc execFile()) khi:
- Lệnh chạy lâu: Ví dụ, một script xử lý dữ liệu hàng giờ.
exec()sẽ giữ bộ đệm mở cho đến khi xong, tốn RAM và không hiệu quả.spawn()cho phép mấy đứa đọc output từng chút một (streaming). - Output quá lớn: Như đã nói, RAM sẽ "khóc thét".
- Cần tương tác "real-time": Nếu mấy đứa cần gửi input cho child process sau khi nó đã bắt đầu chạy, hoặc cần phản ứng ngay lập tức với output của nó,
spawn()là lựa chọn đúng đắn. - Vấn đề bảo mật cao với user input:
execFile()an toàn hơn vì nó chạy trực tiếp file thực thi mà không thông qua shell, tránh được nhiều lỗ hổng shell injection.
Vậy đó, child_process.exec() không chỉ là một công cụ, nó là một "cánh cổng" mở ra vô vàn khả năng cho ứng dụng Node.js của mấy đứa tương tác với thế giới bên ngoài. Hãy dùng nó một cách thông minh và an toàn nhé, các "dev Gen Z" tương lai của anh!
Thuộc Series: Nodejs
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!