-

运行外部程序

Julia 使用倒引号 ` 来运行外部程序:

    julia> `echo hello`
    `echo hello`

它有以下几个特性:

  • 倒引号并不直接运行程序,它构造一个 Cmd 对象来表示这个命令。可以用这个对象,通过管道将命令连接起来,运行,并进行读写
  • 命令运行时,除非指明, Julia 并不捕获输出。它调用 libcsystem ,命令的输出默认指向 stdout
  • 命令运行不需要 shell 。 Julia 直接解析命令语法,对变量内插,像 shell 一样分隔单词,它遵循 shell 引用语法。命令调用 forkexec 函数,作为 julia 的直接子进程。

下面是运行外部程序的例子:

    julia> run(`echo hello`)
    hello

helloecho 命令的输出,它被送到标准输出。 run 方法本身返回 nothing 。如果外部命令没有正确运行,将抛出 ErrorException 异常。

使用 readall 读取命令的输出:

    julia> a=readall(`echo hello`)
    "hello\n"

    julia> (chomp(a)) == "hello"
    true

更普遍的,你可以使用 open 从一个外部命令读取或者写到一个外部命令。例如:

    julia> open(`less`, "w", STDOUT) do io
               for i = 1:1000
                   println(io, i)
               end
           end

内插

将文件名赋给变量 file ,将其作为命令的参数。像在字符串文本中一样使用 $ 做内插(详见 :ref:man-strings ):

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

    julia> `sort $file`
    `sort /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 会被当做一个单变量进行内插,它自动把内容引了起来。事实上,这也不准确: file 的值并不会被 shell 解释,所以不需要真正的引起来;此处把它引起来,只是为了给用户显示。下例也可以正常运行:

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

    julia> name = "data"
    "data"

    julia> ext = "csv"
    "csv"

    julia> `sort $path/$name.$ext`
    `sort '/Volumes/External HD/data.csv'`

如果要内插多个单词,应使用数组(或其它可迭代容器):

    julia> files = ["/etc/passwd","/Volumes/External HD/data.csv"]
    2-element ASCIIString Array:
     "/etc/passwd"
     "/Volumes/External HD/data.csv"

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

如果数组内插为 shell 单词的一部分,Julia 会模仿 shell 的 {a,b,c} 参数生成的行为:

    julia> names = ["foo","bar","baz"]
    3-element ASCIIString Array:
     "foo"
     "bar"
     "baz"

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

如果将多个数组内插进同一个单词,Julia 会模仿 shell 的笛卡尔乘积生成的行为:

    julia> names = ["foo","bar","baz"]
    3-element ASCIIString Array:
     "foo"
     "bar"
     "baz"

    julia> exts = ["aux","log"]
    2-element ASCIIString Array:
     "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`

引用

命令复杂时,有时需要使用引号。来看一个 perl 的命令:

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

再看个使用双引号的命令:

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

一般来说,Julia 的倒引号语法支持将 shell 命令原封不动的复制粘贴进来,且转义、引用、内插等行为可以原封不动地正常工作。唯一的区别是,内插被集成进了 Julia 中:

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

    julia> run(ans)
    0
    1
    2
    3

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

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

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

当需要在 Julia 中运行 shell 命令时,先试试复制粘贴。Julia 会先显示出来命令,可以据此检查内插是否正确,再去运行命令。

管道

Shell 元字符,如 |, &, 及 > 在 Julia 倒引号语法中并是不特殊字符。倒引号中的管道符仅仅是文本化的管道字符 “|” 而已:

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

在 Julia 中要想构造管道,应在 Cmd 间使用 |> 运算符:

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

继续看个例子:

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

它打印 UNIX 系统五个最高级用户的 ID 。 cut, sorttail 命令都作为当前 julia 进程的直接子进程运行,shell 进程没有介入。 Julia 自己来设置管道并连接文件描述符, 这些工作通常由 shell 来完成。也 因此, Julia 可以对子进程实现更好的控制, 也可以实现 shell 不能实现的一 些功能. 值得注意的是, |> 仅仅是重定向了 stdout. 使用 .> 来 重定向 stderr.

Julia 可以并行运行多个命令:

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

输出顺序是非确定性的。两个 echo 进程几乎同时开始,它们竞争 stdout 描述符的写操作,这个描述符被两个进程和 julia 进程所共有。使用管道,可将这些进程的输出传递给其它程序:

    julia> run(`echo world` & `echo hello` |> `sort`)
    hello
    world

来看一个复杂的使用 Julia 来调用 perl 命令的例子:

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

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

这是一个单生产者双并发消费者的经典例子:一个 perl 进程生产从 0 至 9 的 10 行数,两个并行的进程消费这些结果,其中一个给结果加前缀 “A”,另一个加前缀 “B”。我们不知道哪个消费者先消费第一行,但一旦开始,两个进程交替消费这些行。(在 Perl 中设置 $|=1 ,可使打印表达式先清空 stdout 句柄;否则输出会被缓存并立即打印给管道,结果将只有一个消费者进程在读取。)

再看个更复杂的多步的生产者-消费者的例子:

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

此例和前例类似,单有消费者分两步,且两步的延迟不同。

强烈建议你亲手试试这些例子,看看它们是如何运行的。