Perl和操作系统交互(一):system、exec和反引号

调用操作系统命令:system函数

system函数可以直接让perl调用操作系统中的命令并执行。

system入门示例

例如:

#!/usr/bin/perl

system 'date +"%F %T"';
system 'echo hello world';
system 'echo',"hello","world";

执行结果:

2018-06-21 18:32:50
hello world
hello world

注意system的参数可以被单个引号包围,也可以用多个引号分隔成多个参数,如果分隔开,system会将它们用空格的方式连接起来。

另外,上面使用了单引号、双引号,都能正确执行,但注意,双引号会解析perl中的特殊符号。例如:

$myname="Malongshuai";
system "echo $myname";   # 输出:Malongshuai
system 'echo $USER';     # 输出当前登录的用户:root

可见,双引号中的变量$myname被perl解析了,而单引号中的变量$USER不被perl解析,perl将其交给bash,由shell负责解析,所以会输出当前用户名。

在system中,还可以使用shell的重定向、管道等功能。

$myname="Malongshuai";
system "echo $myname >/tmp/a.txt";
print "==============================\n";
system "cat <1.plx";
print "==============================\n";
system 'find . -type f -name "*.pl" -print0 | xargs -0 -i ls -l {}';
system 'sleep 30 &';

深入system

system有两种语法:

system LIST
system PROGRAM LIST

这里忽略第二种,因为它是一种以欺骗的防止执行命令的:LIST中的第一个参数作为命令,但欺骗自己说自己执行的是PROGRAM命令。

下面将详细讨论第一种语法。

基础知识

在讨论之前,先解释一下bash命令行执行命令时的引号解析问题。例如:

awk -F ":" 'NR<=3{username=$1;print "username:",username}' /etc/passwd
find /root -type f -name "*.log"

shell命令行中执行命令时,包含两部分:一个是程序名,一个是程序的参数部分。在真正执行之前,shell的词法分析行为会解析程序名称、参数部分。但有些时候命令行中会使用一些shell的特殊符号来实现shell的特殊功能。例如shell的星号通配符*、管道功能|、重定向功能> < >> << <<<、命令替换功能$()等。但有些程序自身,其用法规则中可能也会使用一些特殊符号(如find -name "*.log"的星号),这会和shell的特殊符号冲突。由于shell的解析行为在命令执行之前,为了保留特殊符号给程序自身来解释,需要使用引号来保护这些特殊符号以避免被shell解析。

正如上面awk中的":"‘{}‘以及find中的"*.log",它们都使用引号包围特殊符号,使得这些符号"逃过"shell的解析过程,从而让程序自身解析。

更通俗一点,如果不是执行命令要依赖于shell环境的存在,如果能直接在最纯粹的环境中执行命令,那么特殊符号是无需加引号保护的。例如,awk如果能脱离shell单独执行,下面的第一条命令才是正确的,第二条命令却是错误的。

awk -F : NR<=3{username=$1;print "username:",username} /etc/passwd
awk -F ":" 'NR<=3{username=$1;print "username:",username}' /etc/passwd

system参数细节

system LIST中的system要求的是列表上下文参数LIST,就像print函数一样。所以,当LIST是一个标量字符串,它其实也是一个列表,只不过是只包含一个元素的列表。

例如:

system 'find /perlapp -type f -name "*.pl"';   # 是一个标量字符串构成的LIST

system "ls","-lh","/root";    # 包含多元素的列表参数

@cmd_arg=qw(-lh /root);
system "ls",@cmd_arg;       # 包含多元素的列表参数

对于system LIST语法,perl在执行LIST中的命令之前,会先检查LIST:

  1. 当system的参数是一个只有单元素的列表(即上面第一个例子),它将检查这个参数整体中是否有需要shell解析的特殊元字符(如shell中的通配符* ? [],shell中的重定向< > >> <<< <<,shell中的管道|,shell的后台任务符号&,命令替换$()等等):

    • 如果有这些需要shell解析的特殊元字符,则调用/bin/sh -c STRING的方式来执行LIST,其中LIST就是STRING部分
    • 如果没有需要shell解析的特殊元字符,则perl将其分割成一个一个单词,并传递给execvp系统函数来执行,它的效率更高
  2. 当system的参数是一个包含多元素的列表:
    • 它将认为列表中的第一个元素是待执行的命令,并直接执行它(按照spawn的方式),而不会先调用bash,再通过bash shell来解析并执行它。
    • 所以,使用多元素的列表参数时,将失去shell中重定向、管道、命令替换等等功能
    • 但如果第一个元素作为命令spawn失败(和语法、参数等无关,而是权限或其它系统层面的失败),将降级回使用shell来执行

注:bash -c STRING的c选项会从STRING中读取命令并执行。

几个示例:

@arg1=qw(-lh /root);
system "ls",@arg1;          # 1.可正确执行

system "ls -lh /root/*.log"; # 2.可正确执行

@arg2=qw(-lh /root/*.log);
system "ls",@arg2;           # 3.将执行失败

system "ls -lh","/root";     # 4.执行失败,更准确的是spawn过程就失败
system "ls","-lh /root";     # 5.执行失败
system "ls","-l -h","/root"; # 6.执行失败

上面第二个system能执行成功,而第三个system会执行失败,是因为:

  • 第二个system的参数是一个单元素的列表,而且有需要解析的通配星号字符,所以它等价于/bin/sh -c ls -lh /root/*.log命令
  • 第三个system的参数是多个元素构成的列表,所以它会直接spawn一个ls进程,由于不在shell环境中执行,ls程序又不认识星号字符,所以执行失败

第四个system也执行失败,因为不止一个参数,于是取第一个参数作为命令来spawn新的进程,但这第一个参数是ls -lh整体,而不是ls,这等价于"ls -lh" /root,所以spawn失败,找不到这个命令。

第5个system执行失败,因为"-lh /root"作为列表的第二个元素,它是一个整体。所以它等价于ls "-lh /root",这显然是错误的。

第6个system执行失败,原因同上。

所以可以稍微总结下,如果使用多个参数的system,每个原本在unix shell命令行中需要空格分开的选项和参数,都需要单独作为列表的独立元素

正如:

system "ls","-lh","/root";

@args=qw(-lh /root);
system "ls",@args;

更复杂一点的示例:

@cmd_arg1=qw(/perlapp -type f -name *.pl);
system "/usr/bin/find",@cmd_arg1;        # 1.正确

@cmd_arg2=qw(/perlapp -type f -name "*.pl");   # 加上了双引号
system "/usr/bin/find",@cmd_arg2;        # 2.错误

$prog="/usr/bin/awk";
@arg3=("-F",":",'NR<=3{username=$1;print "username: ",username}','/etc/passwd');
system $prog,@arg3;      # 3.正确

上面第二个system中,是多参数的system,不会调用shell来解析,而*.pl使用了引号包围,但对于find来说,引号不可识别的字符,它会将其当作要查找文件名的一部分,所以执行失败。之所以在shell命令中的find要加上引号,是为了防止*被shell解析。

第三个system中,没有使用qw()的方式生成列表,因为awk的表达式部分存在空格,使用qw生成列表的方式无法保留空格,所以这里采用最原始的生成列表的形式。当然,也可以实现split来生成:

@arg3=split /%/,q(-F%:%NR<=3{username=$1;print "username: ",username}%/etc/passwd);

使用单个参数还是多参数?

关于使用单个参数的system还是使用多参数的system。

如果对shell解析熟悉,使用单个参数比较好,能比较直接地使用shell相关的功能(重定向、管道等)。但使用单个参数,引号引用和转义引用方面毕竟比较复杂,容易出错,可能需要多次调试。

多个参数也有好处,不用担心太多引号问题,但却失去了使用shell功能的能力。如果想要在多参数的system中使用管道、重定向等特殊符号带来的shell功能,可以将‘/bin/sh‘,‘-c‘作为system的前两个参数,使得system强制调用shell来执行命令。

/bin/sh -c STRING执行命令的方式是shell从STRING中读取命令来执行。所以,为了保证完整性,STRING部分建议全都包含在一个引号中。例如:

shell> bash -c 'find . -type f -name "*.pl" | xargs ls -l'

回到system的调用/bin/sh -c的用法,例如:

$arg1=q(find . -type f -name "*.pl" -print0);    # 1
$arg2=q( | xargs -0 -i ls -l {});                # 2
system '/bin/sh','-c',"$arg1 $arg2";             # 3

上面3行,每行都有关键点:

  • 第一行:

    • 不能使用数组、列表,而是标量的字符串
    • 因为要给shell解析,所以*.pl还是要加上引号包围
  • 第二行:
    • 同样,不能使用数组、列表,而是标量字符串
    • 即使是特殊的管道符号(或其它符号),也可以直接放在标量字符串中
  • 第三行:
    • 前两个参数是/bin/sh-c
    • 第三个参数必须是字符串STRING,强烈建议使用引号包围,保证参数的完整性
    • 如果不加引号包围STRING,而是将arg1和arg2作为参数列表的两个元素,将割裂两者,导致只执行到$arg1中的命令,甚至有时候会因为$arg1不完整或有多余字符而报错

看上去规则很多,而且书写必须十分规范,失之毫厘,结果将差之千里。如非必须,还不如直接写成单个参数的system。例如,上面的3行等价于:

system '/bin/sh','-c','find . -type f -name "*.pl -print0 | xargs -0 -i ls -l {}"';
system 'find . -type f -name "*.pl -print0 | xargs -0 -i ls -l {}"';

捕获system的错误状态

system执行命令时的返回值为$?,它和bash的$?不太一致。当最后一个管道关闭时、反引号执行命令、wait()或waitpid()成功执行时或system(),都会返回$?。在Perl中,$?包含两部分共16字节,低8位是信号信息,高8位才是所执行的命令的状态码。也就是说,perl中的$?的高8位才对应bash中的$?

因此,要获取退出状态码,需要使用$?>>8

#!/usr/bin/perl

system '(exit 4)';
print $?>>8,"\n";    # 输出4

如果,想要直接在执行的命令上判断命令是否正确执行,然后决定是否die。可以在system的前面加上一个!取反。这是因为在shell中,非0的状态码表示命令错误执行,0状态码才表示执行正确。这和perl的布尔值正好相反,所以加上感叹号取反:

!system '(exit 4)' or die "command return error num: ",$?>>8;

需要注意,这里不能使用$!,在perl中有多种不同的错误捕获变量,$!捕获的是perl在发起系统调用层面的错误,而system执行的命令的错误发生在命令执行时。对于system函数来说,perl只要成功执行system,不管里面的命令是否执行成功,perl发起的系统调用都已经结束了。

关于如何获取信号信息,参见官方手册。或者:

The “low” octet combines several things. The highest bit notes if a core dump happened.The hexadecimal and binary representations (recall them from Chapter 2) can help mask out the parts you don’t want:

my $low_octet = $return_value & 0xFF; # mask out high octet
my $dumped_core = $low_octet & 0b1_0000000; # 128
my $signal_number = $low_octet & 0b0111_1111; # 0x7f, or 127

system的内部细节

在Perl中,除了system,还有exec、fork、pipe、IPC等进程操作方式,在后文会一一解释。此处先解释system执行的细节。

在执行到system时,system会直接拷贝一份当前perl进程(称为子进程),然后自己进入睡眠态,并使用wait()等待子进程执行完毕。

因为是直接拷贝的,所以子进程初始时和perl父进程是完全一致的。所以,标准输入(STDIN)、标准输出(STDOUT)、标准错误输出(STDERR)都是和父进程共享的。

system 'read -p "enter your name: " name;echo "your name is: " $name';

在system中的命令执行之前,perl首先会解析system的参数列表,关于解析的方式,在前文已经详细解释过了。如果命令是直接执行的,则命令所在进程就是perl进程的子进程。如果命令需要通过通过调用/bin/sh -c来执行,则shell进程是子进程,真正执行的命令则是孙进程(grandchild)或者是下一代。
例如,在参数中放入shell的for循环,因为这是bash内置属性,它会直接在当前bash进程中完成。

system 'for i in {1..10};do echo $i;done';

这些内容比较复杂,可参见:bash内置命令的特殊性,后台任务的"本质"

当命令执行完毕后,将回到perl进程,perl进程会执行wait(),然后结束system。

调用操作系统命令:exec

exec和system除了一种行为之外,其它用法和system完全一致。exec和system的区别之处在于:

  • system会创建子进程,然后自己进入睡眠,去等待子进程执行完毕,最后执行wait()
  • exec不会创建子进程,而是在当前Perl进程自身去执行命令,相当于用命令去覆盖当前进程,所以没有睡眠
  • 当exec执行的命令结束后,将直接结束当前perl进程,没有wait()行为

由于exec执行完命令后,立即退出当前perl进程,所以命令执行的正确与否,无法被捕获。但如果exec启动待执行命令过程就出错了,这属于perl的系统调用过程出错,可以使用$!捕获。

exec 'date';
die "date couldn't run: $!";

一般来说,很少直接使用exec,而是fork+exec同时使用。关于fork,见后文。

调用操作系统命令:反引号和qx()

原文地址:https://www.cnblogs.com/f-ck-need-u/p/9691714.html

时间: 2024-07-31 02:19:56

Perl和操作系统交互(一):system、exec和反引号的相关文章

php -- PHP在linux上执行外部命令,system(),exec(),shell_exec()

目录:一.PHP中调用外部命令介绍二.关于安全问题三.关于超时问题四.关于PHP运行linux环境中命令出现的问题 一.PHP中调用外部命令介绍 在PHP中调用外部命令,有三种方法: 1. 调用专门函数 2. 反引号 3. popen()函数打开进程 方法一:调用PHP提供的专门函数(四个): PHP提供4个专门的执行外部命令的函数:exec(), system(), passthru(), shell_exec() 1)exec() 原型: string exec ( string $comm

【转】PHP 执行系统外部命令 system() exec() passthru()

PHP作为一种服务器端的脚本语言,象编写简单,或者是复杂的动态网页这样的任务,它完全能够胜任.但事情不总是如此,有时为了实现某个功能,必须借助于操作系统的外部程序(或者称之为命令),这样可以做到事半功倍. 区别: system() 输出并返回最后一行shell结果. exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面. passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上. 相同点:都可以获得命令执行的状态码 demo: //s

PHP 执行系统外部命令 system() exec() passthru()

PHP中调用外部命令,可以用如下三种方法来实现: 方法一:用PHP提供的专门函数(四个): PHP提供4个专门的执行外部命令的函数:exec(), system(), passthru(), shell_exec() 1)exec() 原型: string exec ( string $command [, array &$output [, int &$return_var ]] ) 说明: exec执行系统外部命令时不会输出结果,而是返回结果的最后一行.如果想得到结果,可以使用第二个参

Java和操作系统交互细节

结合 CPU 理解一行 Java 代码是怎么执行的根据冯·诺依曼思想,计算机采用二进制作为数制基础,必须包含:运算器.控制器.存储设备,以及输入输出设备,如下图所示.我们先来分析 CPU 的工作原理,现代 CPU 芯片中大都集成了,控制单元,运算单元,存储单元.控制单元是 CPU 的控制中心, CPU 需要通过它才知道下一步做什么,也就是执行什么指令,控制单元又包含:指令寄存器(IR ),指令译码器( ID )和操作控制器( OC ). 当程序被加载进内存后,指令就在内存中了,这个时候说的内存是

PHP 执行系统外部命令的方法 system() exec()

PHP作为一种服务器端的脚本语言,像编写简单.或者是复杂的动态网页这样的任务,它完全能够胜任.但事情不总是如此,有时为了实现某个功能,必须借助于操作系统的外部程序(或者称之为命令),这样可以做到事半功倍. 那么,是否可以在PHP脚本中调用外部命令呢?如果能,如何去做呢?有些什么方面的顾虑呢?是否可以?答案是肯定的.PHP和其它的程序设计语言一样,完全可以在程序内调用外部命令,并且是很简单的:只要用一个或几个函数即可. //system('dir'); // exec ('dir'); // pa

疯狂java笔记(五) - 系统交互、System、Runtime、Date类

一.程序与用户交互(Java的入口方法-main方法): 运行Java程序时,都必须提供一个main方法入口:public static void main(String[] args){} public:因为main方法里边可能包含同一包内或其他类的方法,为了保证能够正常执行该方法所以只能用该方法; static:调用主方法的时候不会先创建该主类的对象,而是直接通过该类来调用主方法的,所以使用static修饰; String[]:谁调用方法谁就为这个形参赋值,默认长度为0的数组 运行时:jav

Linux操作系统基础学习中,双引号、单引号、反引号的区别及样例

1.双引号("") 由双引号括起来的字符,一般保留特殊字符的功能,如美元符号($).反引号(``).反斜线(\). 2.单引号('') 由单引号括起来的字符都被视为普通字符对待. 3.反引号(``)(在键盘的左上端) 由反引号括起来的字符串被当做shell命令执行,其标准输出结果取代整个反引号部分. 一般都会在命令中这三种引号都会组合起来使用,来组合成更多的命令. 且单引号.双引号都引用时,以命令行最外面的引号为准. 样例 单个应用案例 组合应用案例 单引号.双引号组合案例 原文地址:

Perl基础速成

本文是针对没有Perl基础,但想用perl一行式命令取代grep/awk/sed的人,用于速学Perl基础知识. Perl一行式系列文章:Perl一行式程序 perl的-e选项 perl命令的-e选项后可以书写表达式,例如: perl -e 'print "hello world\n"' Perl中的函数调用经常可以省略括号,所以print "hello world\n"表示的是print("hello world\n"),但并非总是可以省略括号

perl基本语法

标量 标量是 Perl 中最简单的数据类型.大多数的标量是数字(如 255 或 3.25e20)或者字符串(如 hello或者盖茨堡地址). 数字 perl中所有数字内部的格式都是双精度浮点数. 浮点数 1.25 255.000 255.0 7.25e45 #7.25x10 的 45 次方(一个大整数) -6.5e24 # -6.5x10 的 24 次方(一个大的负数) -12e-24 #- -12x10 的-24 次方(很小的负数) -1.2E-23 #指数符号可以大写(E) 整数 0 200