在我有限的编程生涯中,接触过的编程语言就有
python
、php
、java
、javascript(node.js)
、golang
、visual basic
、matlab
、c++
、c
等,除了熟练拼写各编程语言的名字外,写点helo world
也不在话下😆。而现在能在我日常工作中使用到的语言,只有前面5种了。编程过程中,有个经常用到的功能就是调用外部命令,但每次用到都要小心确认,生怕弄错。于是,本文就开始盘点总结了。
0x01 python
python调用外部命令,一般两种。基于os模块或基于subprocess模块。
基于os模块
1. os.system(command)
启动子进程,在子进程中执行
command
,返回子进程执行command后的退出状态码。该种方式会阻塞主进程。
import os
retcode = os.system('ls /root') # retcode = 0
如果正常退出,则retcode=0
, 否则retcode<>0
; 而ls /root
的返回会直接在标准输出显示; 这种方式适用于子进程没有输出结果的情况.
2. os.popen(command [, mode])
打开一个与
command
进程之间的管道。这个函数的返回值是一个文件对象,可以读或者写(由mode
决定,mode
默认是'r'
)。如果mode
为'r'
,可以使用此函数的返回值调用read()
或readlines()
来获取command
命令的执行结果。该种方式会阻塞主进程。
import os
out1 = os.popen('ls /root').read() # out1即为ls /root命令执行结果
p = os.popen('cat /home/.env')
out2 = p.readlines() # out2是按行分的列表
p.close() # 关闭管道, 没有返回值
基于subprocess模块
subprocess
模块的引入就是为了代替os.system()
、os.popen()
、os.spawn()
的,相比后者,前者更优秀,你可以得到标准输出、标准错误、真正的状态码以及更好的错误处理等。
1. subprocess.call(args [, shell=False])
子进程调用命令
args
, 其中args
可以是字符串形式, 也可以是数组形式;shell
参数默认为False
,若为True
,则表示命令最终在shell
中运行。返回值是子进程命令后的退出状态码,命令的输出结果直接在标准输出显示。官方出于安全考虑, 不建议使用shell=True
。
import subprocess
retcode = subprocess.call(['ls', '-l'])
retcode = subprocess.call('ls -l', shell=True)
subprocess.check_call(args [, shell=False])
用法类似。
2. subprocess.check_output(args [, shell=False])
子进程调用
args
命令,如果执行成功则返回执行结果,否则抛异常。
import subprocess
result = subprocess.check_output(['ls', '-l'])
result = subprocess.check_output('ls -l', shell=True)
3. subprocess.Popen(...)
用于执行复杂的系统命令, 返回值为管道。
import subprocess
ret1 = subprocess.Popen(['ls', '-l'])
ret2 = subprocess.Popen('ls -l', shell=True)
ret3 = subprocess.Popen('ls -l', shell=True, cwd='/home') # cwd参数表示进入指定目录执行
# 新开一个python3进程并执行两条命令
obj = subprocess.Popen(['python3'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
# out_error_list = obj.communicate('print("hello")')
obj.stdin.write('print(1)\n')
obj.stdin.write('print(2)')
out_error_list = obj.communicate() # 获取标准输出和标准错误元组
obj.stdin.close()
# 获取标准输出和标准错误
cmd_out = obj.stdout.read() # '1\n\2\n'
obj.stdout.close()
cmd_error = obj.stderr.read() # ''
obj.stderr.close()
0x02 php
php调用外部命令,有四种方式。
1. system(string $command [, int &$return_var ]) : string
返回输出的最后一行, 前面的行打印在屏幕
2. passthru(string $command [, int &$return_var ]) : void
不返回, 输出内容打印在屏幕
3. shell_exec(string $command) : string
输出全部返回
4. exec(string $command [, array $rs]) : string
返回输出最后一行
0x03 java
java调用外部命令一般通过
Runtime
模块。
Runtime.getRuntime().exec(command)
但是这样执行时没有任何输出,因为调用
Runtime.exec
方法将产生一个本地的进程,并返回一个Process
子类的实例,该实例可用于控制进程或取得进程的相关信息。
由于调用Runtime.exec
方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(stdin
,stdou
,stderr
)都通过 Process.getOutputStream()
,Process.getInputStream()
, Process.getErrorStream()
方法重定向给它的父进程了。
用户需要用这些stream来向子进程输入数据或获取子进程的输出,下面的代码可以取到命令的执行结果。
import java.io.InputStreamReader;
import java.io.BufferedReader;
public class test {
public static void main(String[] args) {
try {
String[] cmd = new String[]{"/bin/sh", "-c", "ls"};
Process ps = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
String result = sb.toString();
System.out.println(result);
ps.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
0x04 javascript(node.js)
javascript(node.js)调用外部命令一般使用
child_process
模块,顾名思义,这个模块就是通过子进程来处理任务的。
1. exec
与execSync
这是child_process模块里面最简单的函数,作用就是执行一个固定的系统命令。
const { exec } = require('child_process');
exec('ls -l', ['/var/www'], (err, stdout, stderr) => {
if(err) {
console.log(err);
return;
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
});
exec
函数第一个参数是要执行的命令,第二个参数是配置选项,第三个参数是回调函数,第二个参数中一个比较常用的就是子进程的工作目录。
const { exec } = require('child_process');
const path = require('path');
exec('ls -al', { cwd: path.join(process.cwd(), 'scripts') }, (err, stdout, stderr) => {
if(err) {
console.log(err);
return;
}
console.log(`stdout: ${stdout}`);
});
execSync
是exec
的同步版本,不过无论是execSync
还是exec
,得到的结果都是字符串或者Buffer
对象,一般需要进一步处理。
2. execFile
与execFileSync
这两个函数的作用是执行一个可执行文件(命令),看下面的实例。
const { execFile, execFileSync } = require('child_process');
const StringDecoder = require('string_decoder').StringDecoder;
const decoder = new StringDecoder('utf8');
execFile('curl', ['-I', 'cip.cc'], (err, stdout, stderr) => {
if(err) {
console.log(err);
return;
}
console.log(`stdout: ${stdout}`);
});
const stdout = execFileSync('node', ['-v']);
const str = decoder.write(stdout);
console.log(str);
跟
exec
类似,第一个参数是要执行的文件路径,第二个是参数数组,第三个是回调函数,当然,除了第一个之外都是可以省略的。
3. spawn
与spawnSync
child_process
模块中所有函数都是基于spawn
和spawnSync
函数的来实现的,换句话来说,spawn
和spawnSync
函数的配置是最完全的,其它函数都是对其做了封装和修改。下面我们来重点讲解一下:
spawn
函数原型是这样的:child_process.spawn(command[, args][, options])
它使用指定的命令行参数创建新进程,spawn
会返回一个带有stdout
和stderr
流的对象。你可以通过stdout
流来读取子进程返回给Node.js
的数据。stdout
拥有'data'
,'end'
以及一般流所具有的事件。当你想要子进程返回大量数据给Node
时,比如图像处理,读取二进制数据等,你最好使用spawn
方法。
const {spawn} = require('child_process');
const fs = require('fs');
const spawnObj = spawn('curl', ['cip.cc'], {encoding: 'utf-8'});
spawnObj.stdout.on('data', function(chunk) {
console.log(chunk.toString());
});
spawnObj.stderr.on('data', (data) => {
console.log(data);
});
spawnObj.on('close', function(code) {
console.log(`close code : ${code}`);
})
spawnObj.on('exit', (code) => {
console.log(`exit code : ${code}`);
let fd = fs.openSync('test.txt', 'r');
fs.close(fd, function(err) {
if(err) {
console.error(err);
}
});
});
0x05 golang
golang调用外部命令,通过
os/exec
包执行。它将os.StartProcess
进行包装使得它更容易映射到stdin
和stdout
。
下面的代码将执行bash -c 'ls -ltr'
命令,并打印出标准输出和标准错误。
package main
import (
"bytes"
"fmt"
"log"
"os/exec"
)
const ShellToUse = "bash"
func Shellout(command string) (error, string, string) {
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command(ShellToUse, "-c", command)
// exec.Command(command).Output() 非shell运行, 直接获取字节输出
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return err, stdout.String(), stderr.String()
}
func main() {
err, out, errout := Shellout("ls -ltr")
if err != nil {
log.Printf("error: %v\n", err)
}
fmt.Println("--- stdout ---")
fmt.Println(out)
fmt.Println("--- stderr ---")
fmt.Println(errout)
}
总结
无论何种编程语言,要想调用外部任务(系统命令),都绕不过启动一个子进程,在子进程里调用外部命令,得到标准输出stdout
和标准错误stderr
。