Engee 文档

外部程序的执行

Julia借用了shell、Perl和Ruby命令的后撇号表示法。 然而,在朱莉娅,拼写

julia> `echo hello`
`echo hello`

它与各种shell,Perl或Ruby中的行为有许多不同之处。

  • 相反的撇号不是立即执行命令,而是创建一个对象。 'Cmd'来表示命令。 您可以使用此对象使用传输通道将命令连接到其他命令,并使用函数执行它 'run',使用函数读取它 'read'或使用函数写入它 '写`

  • 当执行一个命令时,Julia不会记录它的输出,除非你已经为此特别提供了。 默认命令输出被发送到一个常量 'stdout',就像使用库的`system`调用(libc)一样。

  • 该命令永远不会在shell中执行。 Julia直接分析命令的语法,相应地插入变量并执行单词分离,就像shell所做的那样,观察shell中引号的语法。 该命令使用"fork"和"exec"调用作为"julia"的直接子进程执行。

以下示例假设Posix环境,如Linux或macOS。 在Windows中,许多类似的命令,如`echo`和`dir`,不是外部程序,而是嵌入到文件本身的cmd中。exe的炮弹。 运行这些命令的一个选项是调用文件’cmd。exe`,例如,'cmd/C echo hello'。 或者,Julia可以在Posix环境中运行,例如Cygwin。

下面是一个执行外部命令的简单示例。

julia> mycommand = `echo hello`
`echo hello`

julia> typeof(mycommand)
Cmd

julia> run(mycommand);
hello

'hello`是’echo’命令的输出,发送到一个常量 'stdout'。 如果外部命令的执行失败,则执行方法引发异常。 'ProcessFailedException'

如果需要读取外部命令的输出,请使用以下函数 '阅读''readchomp'

julia> read(`echo hello`, String)
"hello\n"

julia> readchomp(`echo hello`)
"hello"

更一般地说,要从外部命令读取或写入,可以使用函数 '打开`

julia> open(`less`, "w", stdout) do io
           for i = 1:3
               println(io, i)
           end
       end
1
2
3

可以访问和迭代命令中的程序名和单个参数,就像命令是一个字符串数组一样。

julia> collect(`echo "foo bar"`)
2-element Vector{String}:
 "echo"
 "foo bar"

julia> `echo "foo bar"`[2]
"foo bar"

插值法

假设你需要做一些更复杂的事情,并使用’file’变量中的文件名作为命令参数。 您可以使用`$`进行插值,方法与字符串文字中的方法相同(请参阅部分 线)。

julia> file = "/etc/passwd"
"/etc/passwd"

julia> `sort $file`
`sort /etc/passwd`

当通过shell运行外部程序时,经常会出现以下问题:如果文件名中包含对shell特殊的字符,它们可能会导致不良行为。 例如,假设您需要对`/etc/passwd`文件的内容进行排序,但对'/Volumes/External HD/data进行排序。csv’文件。 让我们试着做到这一点。

julia> file = "/Volumes/External HD/data.csv"
"/Volumes/External HD/data.csv"

julia> `sort $file`
`sort '/Volumes/External HD/data.csv'`

文件名是如何用引号括起来的? Julia知道’file’变量用于插值作为唯一的参数,所以这个词用引号括起来。 事实上,这并不完全准确:shell从不解释`file`变量的值,因此不需要实际的引号。 插入引号仅用于向用户显示。 即使在将值作为shell字的一部分进行插值时,这也会起作用。

julia> path = "/Volumes/External HD"
"/Volumes/External HD"

julia> name = "data"
"data"

julia> ext = "csv"
"csv"

julia>'排序$path/name name。$分机`
"排序"/卷/外部HD/数据。csv"

正如你所看到的,`path`变量中的空格被相应地转义。 但是如果你需要插几句话呢? 在这种情况下,只需使用数组(或任何其他可迭代容器)。

julia> files = ["/etc/passwd","/Volumes/External HD/data.csv"]
2-element Vector{String}:
 "/etc/passwd"
 "/Volumes/External HD/data.csv"

julia> `grep foo $files`
`grep foo /etc/passwd '/Volumes/External HD/data.csv'`

如果将数组作为shell字的一部分进行插值,Julia将模拟参数生成。 {a,b,c} 贝壳。

julia> names = ["foo","bar","baz"]
3-element Vector{String}:
 "foo"
 "bar"
 "baz"

julia> `grep xylophone $names.txt`
`grep xylophone foo.txt bar.txt baz.txt`

此外,当将多个数组插值到单个单词中时,外壳形成笛卡尔积的行为被模拟。

julia> names = ["foo","bar","baz"]
3-element Vector{String}:
 "foo"
 "bar"
 "baz"

julia> exts = ["aux","log"]
2-element Vector{String}:
 "aux"
 "log"

julia> `rm -f $names.$exts`
`rm -f foo.aux foo.log bar.aux bar.log baz.aux baz.log`

由于您可以插入文字数组,因此无需创建临时数组对象即可使用此生成功能。

julia> `rm -rf $["foo","bar","baz","qux"].$["aux","log","pdf"]`
`rm -rf foo.aux foo.log foo.pdf bar.aux bar.log bar.pdf baz.aux baz.log baz.pdf qux.aux qux.log qux.pdf`

用引号括起来

开发人员不可避免地想要编写复杂的命令,因此您应该考虑使用引号。 下面是shell提示符中的一行Perl代码的简单示例。

sh$ perl -le '$|=1; for (0..3) { print }'
0
1
2
3

Perl表达式应该用单引号括起来,原因有两个:这样空格就不会将表达式分割成多个shell单词,并且使用Perl变量,如`$|`(是的,这是Perl变量的名称)不会导致插值。 在其他情况下,您可以使用双引号进行插值。

sh$ first="A"
sh$ second="B"
sh$ perl -le '$|=1; print for @ARGV' "1: $first" "2: $second"
1: A
2: B

一般来说,Julia的背撇号的语法是经过仔细考虑的,所以你可以简单地剪切和粘贴shell命令,因为它们在后撇号中,它们将起作用:转义,引用和插值将以与shell中 唯一的区别是插值是集成的,并考虑了Julia的想法,即什么是单个字符串值,什么是多个值的容器。 让我们尝试在Julia中运行上面的两个例子。

julia> A = `perl -le '$|=1; for (0..3) { print }'`
`perl -le '$|=1; for (0..3) { print }'`

julia> run(A);
0
1
2
3

julia> first = "A"; second = "B";

julia> B = `perl -le 'print for @ARGV' "1: $first" "2: $second"`
`perl -le 'print for @ARGV' '1: A' '2: B'`

julia> run(B);
1: A
2: B

结果是相同的,Julia的插值行为模仿了shell的行为,一些改进与Julia支持可迭代的一流对象有关,而大多数shell为此使用由一定空间分隔的字符串,这会产生歧义。 当尝试将shell命令传输到Julia时,请先尝试剪切和粘贴它们。 由于Julia在执行命令之前显示命令,因此您可以简单地研究它们的解释而不会产生任何负面后果。

管道

诸如`|&`和`>`之类的Shell元字符必须在Julia back撇号内用引号括起来(或转义)。

julia> run(`echo hello '|' sort`);
hello | sort

julia> run(`echo hello \| sort`);
hello | sort

这个表达式调用’echo’命令,其中有三个单词作为参数``hello`,|`和’sort'。 结果是一行:'hello/sort'。 那我们怎样才能建立管道呢? 而不是在反向撇号内使用|",而是使用管道('管道')。

julia> run(pipeline(`echo hello`, `sort`));
hello

'Echo’命令的输出传递给’sort’命令。 当然,这不是很有趣,因为你只需要排序一行,但你可以执行更显着的动作。

julia> run(pipeline(`cut -d: -f3 /etc/passwd`, `sort -n`, `tail -n5`))
210
211
212
213
214

UNIX系统中最高的五个用户ID值显示在这里。 'Cut`,`sort`和’tail’命令是作为当前`julia’进程的直接子进程生成的,而没有中间shell进程。 Julia独立执行配置文件描述符的传输和连接的工作,这通常由shell完成。 因为Julia自己这样做,它提供了最大的控制,并且可以实现shell不能实现的东西。

Julia可以并行执行多个命令。

julia> run(`echo hello` & `echo world`);
world
hello

这里的输出顺序是非确定性的,因为两个echo进程几乎同时运行,并且首先竞争写入描述符的权利。 'stdout',这是他们和父进程`julia’共同的。 Julia允许您将这两个进程的输出传输到另一个程序。

julia> run(pipeline(`echo world` & `echo hello`, `sort`));
hello
world

从UNIX流水线的角度来看,这里发生的情况是一个UNIX传输通道对象由两个echo进程创建和写入,传输通道的另一端由sort命令读取。

可以通过将命名参数’stdin`,`stdout`和`stderr`传递给`pipeline’函数来执行I/O重定向。

pipeline(`do_work`, stdout=pipeline(`sort`, "out.txt"), stderr="errs.txt")

防止管道死锁

当读取管道的两端并从同一进程写入它们时,避免内核强制缓冲所有数据的情况非常重要。

例如,当读取命令的整个输出时,应该调用`read(out,String)`函数,而不是`wait(process)',因为前者将主动消耗进程写入的所有数据,而后者将尝试将数据保存到内核缓冲区,同时

另一种常见的解决方案是将管道的读取对象和写入对象分离为单独的任务('任务')。

writer = @async write(process, "data")
reader = @async do_compute(read(process, String))
wait(writer)
fetch(reader)

(通常,读取对象不是一个单独的任务,因为我们无论如何都使用`fetch’立即检索它。)

一个复杂的例子

高级编程语言,一流的命令抽象和进程之间的自动通道配置的组合是一个强大的工具。 为了让您了解可以轻松创建的复杂管道,以下是一些更复杂的示例。 我们为过度使用Perl单行表示歉意。

julia> prefixer(prefix, sleep) = `perl -nle '$|=1; print "'$prefix' ", $_; sleep '$sleep';'`;

julia> run(pipeline(`perl -le '$|=1; for(0..5){ print; sleep 1 }'`, prefixer("A",2) & prefixer("B",2)));
B 0
A 1
B 2
A 3
B 4
A 5

这是一个生产者如何支持两个并行消费者的典型例子:一个perl进程生成数字从0到5的字符串,两个并行进程消耗这个输出,其中一个将字母A作为前缀添加到字符串中,另一个将字母B添加到字符串中。 (当在Perl中设置'$/=1`时,每个输出语句都会清除句柄 'stdout',这是这个例子工作所必需的。 否则,整个输出被缓冲并立即输出到传输通道,以便只有一个消费者进程可以读取它。)

这是一个更复杂的"生产者-消费者"的多阶段示例。

julia> run(pipeline(`perl -le '$|=1; for(0..5){ print; sleep 1 }'`,
           prefixer("X",3) & prefixer("Y",3) & prefixer("Z",3),
           prefixer("A",2) & prefixer("B",2)));
A X 0
B Y 1
A Z 2
B X 3
A Y 4
B Z 5

此示例与前一个示例类似,不同之处在于它有两个阶段用于使用者,并且阶段具有不同的延迟,因此它们使用不同数量的并行工作流来保持最大吞吐量。

一定要尝试所有这些例子,看看它们是如何工作的。

"Cmd"对象

反向撇号的语法创建类型的对象 'Cmd'。 这样的对象也可以直接从现有的"Cmd"对象或参数列表构建。

run(Cmd(`pwd`, dir=".."))
run(Cmd(["pwd"], detach=true, ignorestatus=true))

这允许您使用命名参数指定`Cmd`运行时的几个方面。 例如,关键字’dir’控制工作目录’Cmd'。

julia> run(Cmd(`pwd`, dir="/"));
/

关键字’env’允许您设置运行时变量。

julia> run(Cmd(`sh -c "echo foo \$HOWLONG"`, env=("HOWLONG" => "ever!",)));
foo ever!

有关其他命名参数的列表,请参阅说明 'Cmd'。 命令 'setenv''addenv'分别用于替换或添加到`Cmd’运行时变量。

julia> run(setenv(`sh -c "echo foo \$HOWLONG"`, ("HOWLONG" => "ever!",)));
foo ever!

julia> run(addenv(`sh -c "echo foo \$HOWLONG"`, "HOWLONG" => "ever!"));
foo ever!