Gereksinimler ve Ön Hazırlık
Bu rehberdeki kod örneklerini çalıştırabilmek için bilgisayarınızda güncel bir Node.js sürümü (LTS sürümü önerilir) yüklü olmalıdır. Çalışma ortamınızı doğrulamak için terminalinize node -v yazarak sürümünüzü kontrol edebilirsiniz.
- Node.js: v20.x veya üzeri sürüm.
- İşletim Sistemi: Windows, macOS veya Linux (Komutlar işletim sistemine göre değişiklik gösterebilir).
- Düzenleyici: VS Code gibi bir kod editörü.
Projenizi başlatmak için boş bir klasör oluşturun ve terminalde npm init -y komutuyla bir package.json dosyası oluşturun. Bu, bağımlılıklarınızı yönetmek için gerekli olan temel yapılandırmayı sağlayacaktır.
Child Process Modülü Nedir ve Neden Kullanılır?
Node.js, tek bir iş parçacığı (single-threaded) üzerinde çalışır. Bu, ağır bir sistem işleminin ana döngüyü bloke etme riski taşıdığı anlamına gelir. child_process modülü, Node.js'in ana süreçten ayrı, yeni bir süreç (process) oluşturmasına olanak tanır. Böylece, sistem komutlarını asenkron olarak çalıştırabilir ve ana uygulamanızın performansını düşürmeden sonuçları alabilirsiniz.
Bu modül dört ana fonksiyon sunar: spawn, exec, execFile ve fork. Hangi yöntemi seçeceğiniz, çalıştırmak istediğiniz komutun türüne ve veri boyutuna bağlıdır.
exec() Kullanarak Basit Komutlar Çalıştırma
exec() fonksiyonu, bir komutu kabuk (shell) üzerinde çalıştırır ve çıktıyı bir tampon bellekte (buffer) toplar. Küçük boyutlu komutlar ve hızlı sonuçlar için idealdir.
const { exec } = require('child_process');
exec('echo "Merhaba Node.js"', (error, stdout, stderr) => {
if (error) {
console.error(`Hata: ${error.message}`);
return;
}
if (stderr) {
console.error(`Hata Çıktısı: ${stderr}`);
return;
}
console.log(`Çıktı: ${stdout}`);
});
Yukarıdaki örnekte, basit bir echo komutu çalıştırılmıştır. stdout değişkeni başarılı sonucu, stderr ise oluşabilecek hataları yakalar. Bu yöntem, komutun tamamlanmasını bekler ve sonucu tek seferde döndürür.
spawn() ile Büyük Veri ve Sürekli Akış Yönetimi
Komutunuz uzun sürecekse veya çok büyük miktarda veri döndürüyorsa spawn() kullanmalısınız. spawn(), çıktıyı bir akış (stream) olarak verir, bu da belleği verimli kullanmanızı sağlar.
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`Çıktı: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`Hata: ${data}`);
});
ls.on('close', (code) => {
console.log(`Süreç ${code} koduyla sonlandı.`);
});
Bu kodda, ls -lh /usr komutu çalıştırılır. Çıktı parça parça geldiği için data olay dinleyicisi ile veriyi anlık olarak işleyebiliriz. Büyük dosya listelemelerinde veya uzun süren işlemlerde bellek taşmasını önlemek için en güvenli yoldur.
Yöntemlerin Karşılaştırması
| Yöntem | Kullanım Amacı | Avantajı | Dezavantajı |
|---|---|---|---|
| exec | Kısa komutlar | Kullanımı kolay | Büyük veride bellek sorunu |
| spawn | Uzun süreli işlemler | Bellek verimli | Daha karmaşık sözdizimi |
| execFile | Doğrudan dosya çalıştırma | Daha güvenli | Shell özellikleri yok |
Güvenlik: Komut Enjeksiyonundan Korunma
Kritik Uyarı: Kullanıcıdan alınan verileri doğrudan komut satırına göndermek, "Command Injection" (Komut Enjeksiyonu) saldırılarına kapı aralar. Asla kullanıcı girdisini doğrudanexec()içine yerleştirmeyin. Her zaman girdileri doğrulayın ve mümkünsespawn()kullanarak argümanları dizi halinde geçirin.
Güvenli bir yaklaşım için argümanları ayırarak kullanmak, sistemin komutun yapısını bozmasını engeller. Aşağıdaki örnekte, kullanıcı girdisini nasıl güvenli bir şekilde işleyeceğinizi görebilirsiniz.
const { spawn } = require('child_process');
// Kullanıcıdan gelen riskli girdi
const userInput = 'dosya_adi.txt';
// Güvenli yöntem: Argümanları dizi olarak geçirmek
const child = spawn('cat', [userInput]);
child.stdout.on('data', (data) => {
console.log(`İçerik: ${data}`);
});
Hata Yönetimi ve Debug İpuçları
Sistem komutları çalıştırılırken oluşabilecek hataları yönetmek için error olayını dinlemek hayati önem taşır. Ayrıca, komutun bulunamadığı veya yetki hatası aldığı durumları kontrol etmelisiniz.
const { exec } = require('child_process');
exec('invalid_command', (error, stdout, stderr) => {
if (error) {
console.error(`Komut çalıştırılamadı: ${error.code}`);
return;
}
console.log(stdout);
});
Hata ayıklarken, komutun tam yolunu belirtmek (örneğin /usr/bin/node yerine node) bazen PATH sorunlarına yol açabilir. Gerekirse komutlarınızın mutlak yollarını kullanın.
Sorumluluk Reddi: Bu makaledeki kod örnekleri eğitim amaçlıdır. Sistem komutlarını çalıştırmak, sunucunuz üzerinde geniş yetkiler gerektirir. Üretim ortamında (production) bu işlemleri yaparken "en az yetki" (principle of least privilege) prensibine uygun kullanıcılar ile çalışın.
Sıkça Sorulan Sorular
1. exec ve spawn arasındaki temel fark nedir?
exec, komutu bir shell içinde çalıştırır ve tüm çıktıyı bellekte toplar. spawn ise komutu doğrudan bir süreç olarak başlatır ve çıktıyı akış (stream) olarak döndürür.
2. Kullanıcı girdisini nasıl güvenli hale getirebilirim?
Kullanıcı girdisini asla bir string içine gömmeyin. spawn() fonksiyonunun ikinci parametresi olan argüman dizisini kullanın; bu sayede girdi, komutun bir parçası değil, parametresi olarak işlenir.
3. Windows üzerinde farklı bir komut kullanmalı mıyım?
Evet, Windows'ta ls yerine dir gibi komutlar kullanılır. Kodunuzun platformdan bağımsız çalışmasını istiyorsanız, komutları işletim sistemine göre kontrol eden bir yapı kurmalısınız.
4. Neden execFile kullanmalıyım?
execFile, bir shell oluşturmadan doğrudan bir çalıştırılabilir dosyayı (executable) çalıştırır. Bu, shell enjeksiyonu riskini azaltır ve daha performanslıdır.
5. Süreçleri nasıl sonlandırabilirim?
spawn() ile oluşturulan nesnenin child.kill() metodunu kullanarak çalışan süreci durdurabilirsiniz.
İleri Seviye: Promise Yapısı ile Asenkron Süreç Yönetimi
Node.js'in child_process modülü varsayılan olarak olay tabanlı (event-driven) bir yapı sunar. Ancak modern JavaScript uygulamalarında async/await yapısını kullanmak, kodun okunabilirliğini ve hata yönetimini ciddi oranda kolaylaştırır. Aşağıdaki örnekte, spawn fonksiyonunu bir Promise içerisine alarak süreç yönetimini nasıl daha temiz hale getirebileceğinizi görebilirsiniz.
const { spawn } = require('child_process');
function runCommand(command, args) {
return new Promise((resolve, reject) => {
const process = spawn(command, args);
let stdout = '';
let stderr = '';
process.stdout.on('data', (data) => { stdout += data; });
process.stderr.on('data', (data) => { stderr += data; });
process.on('close', (code) => {
if (code === 0) {
resolve(stdout.trim());
} else {
reject(new Error(`Komut ${code} koduyla başarısız oldu: ${stderr}`));
}
});
process.on('error', (err) => reject(err));
});
}
// Kullanımı
(async () => {
try {
const result = await runCommand('ls', ['-lh']);
console.log('Çıktı:', result);
} catch (err) {
console.error('Hata:', err.message);
}
})();
Performans Optimizasyonu ve Kaynak Yönetimi
Sistem kaynaklarını yoğun kullanan süreçleri yönetirken, uygulamanızın performansını korumak için dikkat etmeniz gereken kritik noktalar bulunmaktadır. Özellikle yüksek hacimli veri akışlarında (stream) bellek taşmalarını önlemek için şu stratejileri izlemelisiniz:
1. Bellek Yönetimi ve Buffer Kullanımı
exec kullanırken çıktının tamamı belleğe (RAM) yüklenir. Eğer çalıştırdığınız komut çok büyük bir çıktı üretiyorsa, uygulamanızın çökmesine neden olabilir. Bu gibi durumlarda spawn kullanarak veriyi parça parça (chunk) işlemek çok daha güvenlidir.
2. Süreçlerin İzole Edilmesi
Aynı anda çok sayıda alt süreç (child process) başlatmak, işletim sisteminin dosya tanımlayıcı (file descriptor) limitlerine takılmanıza neden olabilir. İşlemlerinizi bir kuyruk (queue) yapısı ile yönetmek, CPU üzerindeki ani yüklenmeleri engeller.
3. İletişim Protokolü Olarak IPC Kullanımı
Ana süreç ve alt süreç arasında karmaşık veri alışverişi yapmanız gerekiyorsa, stdio: ['inherit', 'pipe', 'pipe', 'ipc'] seçeneğini kullanarak bir IPC (Inter-Process Communication) kanalı açabilirsiniz. Bu, süreçler arasında JSON tabanlı mesajlaşma yapmanıza olanak tanır.
// Alt süreçten ana sürece mesaj gönderme
process.send({ status: 'progress', percent: 50 });
// Ana süreçte mesajı dinleme
child.on('message', (msg) => {
console.log('Alt süreçten gelen mesaj:', msg);
});
4. Timeout Mekanizması
Sonsuz döngüye giren veya yanıt vermeyen süreçleri engellemek için mutlaka bir zaman aşımı (timeout) mekanizması kurun. setTimeout kullanarak belirli bir süre sonunda child.kill() metodunu tetiklemek, uygulamanızın "zombi süreçler" tarafından kilitlenmesini önler.
Sonuç
Node.js ile child_process modülünü kullanmak, uygulamanızın sistem seviyesindeki gücünü artırmanın en etkili yoludur. Bu rehberde öğrendiğiniz spawn ve exec yöntemleri, doğru güvenlik önlemleriyle birleştiğinde oldukça güçlü otomasyon araçlarına dönüşür. Bir sonraki adım olarak, bu süreçleri bir Promise yapısı içerisine alarak async/await ile daha temiz bir kod yapısı oluşturmayı deneyebilirsiniz.

Yorumlar (0)
Yorum Yaz