Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chinese translation for learn/tutorials/command-line_arguments #1605

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 132 additions & 26 deletions site/learn/tutorials/command-line_arguments.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,159 @@
命令行参数
=========

这是一个给初次编写OCaml程序且想知道命令行意义的人看的简略指南。
在这篇教程中,我们将学习如何直接使用 OCaml 的 `Sys.argv` 数列来接受命令行参数,
以及如何使用标准库中的 `Arg` 模块来更简单地完成这件事。

argv
Sys.argv
----

与C语言一样, 传给程序的参数存在一个数组里面。按照惯例,
数组名为`argv`.它可以在标准库的`Sys`模块中找到,
因此它的全称是`Sys.argv`.包括程序名的参数,它们的个数就是此数组的长度.
它可以通过`Array.length`函数取得。
与 C 语言和很多其它语言一样一样,传给程序的参数存在一个数组里面。按照惯例
数组名为 `argv`它可以在标准库的 `Sys` 模块中找到
因此它的全称是 `Sys.argv` 。此数组的长度就是加上程序名本身的命令行参数的个数。
它可以通过 `Array.length` 函数取得。

简单示例
--------

下面的程序显示Sys.argv里的参数以及它们各自在数组中的位置:
下面的程序显示 `Sys.argv` 里的参数以及它们各自在数组中的位置:

```ocaml
open Printf

let () =
for i = 0 to Array.length Sys.argv - 1 do
printf "[%i] %s\n" i Sys.argv.(i)
done
for i = 0 to Array.length Sys.argv - 1 do
Printf.printf "[%i] %s\n" i Sys.argv.(i)
done
```

如果你把上面程序存为`args.ml`,
并且运行`ocaml args.ml arg1 arg2 arg3`,你将得到:
如果你把上面程序存为 `args.ml`
并且执行 `ocaml args.ml arg1 arg2 arg3` ,你将看到:

[0] args.ml
[1] arg1
[2] arg2
[3] arg3

`ocaml` 其实是开启了一个子进程来运行这个argv是
`args.ml arg1 arg2 arg3`的程序. 你也可以用
`ocamlopt -o args args.ml`来编译你的程序,之后运行`./args arg1 arg2 arg3`,你将得到:
需要注意的是,`ocaml` 其实是开启了一个子进程来运行这个 argv 是 `args.ml arg1 arg2 arg3` 的程序。
你也可以用 `ocamlopt -o args args.ml` 来编译你的程序,
之后运行 `./args arg1 arg2 arg3`你将得到:

[0] ./args
[1] arg1
[2] arg2
[3] arg3

解析命令行选项的工具

使用 Arg 模块
----

Ocaml 的标准库中有一个用来编写命令行界面的模块,所以我们不需要直接使用 `Sys.argv`。
我们应考虑的例子是 OCaml 文档中一个用来附加文件的程序。

首先,我们编写程序的使用说明,以在格式不正确的命令行参数被接收或(用户)提请帮助时显示:

```ocaml
let usage_msg = "append [-verbose] <file1> [<file2>] ... -o <output>"
```

接下来,我们创建一些用于接收命令行传来的信息(命令行参数)的引用。 `Arg` 模块将会在命令行被读入时替我们填上它们:

```ocaml
let verbose = ref false

let input_files = ref []

let output_file = ref ""
```

我们有一个布尔型的引用来表示 `-verbose` 是否被打开,它的默认值是 `false`。
然后我们有一个指向列表的引用,它将保存所有输入文件的名称。
最后,我们有一个字符串引用,由 `-o` 指定的单一输出文件名将被放入其中。

我们需要一个函数来处理匿名输入,也就是说,那些前面没有标志(译注:即这些参数是 positional arguments,非 keyword arguments)的(字符串)。
在这种情况下,这些是我们的输入文件名。
我们的函数只是将文件名添加到前面定义的引用中。

```ocaml
let anon_fun filename =
input_files := filename :: !input_files
```

最终,我们建立一个命令行标志指定的列表。每个标志都是一个由标志名称、遇到它时要采取的行动和帮助字符串组成的元组。

```ocaml
let speclist =
[("-verbose", Arg.Set verbose, "Output debug information");
("-o", Arg.Set_string output_file, "Set output file name")]
```

在这里有两种可能操作:`Arg.Set` 设置一个布尔引用,而 `Arg.Set_string` 设置一个字符串引用。
我们的 `input_files` 引用自然会被已经定义的 `anon_fun` 函数所更新。

我们现在可以调用 `Arg.parse` ,给它提供我们的指定的操作列表、匿名函数和使用信息。
一旦它(完成,成功)返回,之前定义的引用内将被填入所有需要附加我们文件的信息。

```ocaml
let () =
Arg.parse speclist anon_fun usage_msg;
(* Main functionality here *)
```

让我们把我们的程序保存为 `append.ml`,然后用 `ocamlopt -o append append.ml` 编译它,并试着运行:

```shell
$./append -verbose one.txt two.txt -o three.txt

$./append one.txt two.txt

$./append -quiet
./append: unknown option '-quiet'.
append [-verbose] <file1> [<file2>] ... -o <output>
-verbose Output debug information
-o Set output file name
-help Display this list of options
--help Display this list of options

$./append -help
append [-verbose] <file1> [<file2>] ... -o <output>
-verbose Output debug information
-o Set output file name
-help Display this list of options
--help Display this list of options
```

这是完整的程序:

```ocaml
let usage_msg = "append [-verbose] <file1> [<file2>] ... -o <output>"

let verbose = ref false

let input_files = ref []

let output_file = ref ""

let anon_fun filename =
input_files := filename :: !input_files

let speclist =
[("-verbose", Arg.Set verbose, "Output debug information");
("-o", Arg.Set_string output_file, "Set output file name")]

let () =
Arg.parse speclist anon_fun usage_msg;
(* Main functionality here *)
```

`Arg` 模块除了 `Set` 和 `Set_string` 之外还有许多操作,还有一些底层函数用于解析更复杂的命令行。


其它用于解析命令行选项的工具
--------------------

有一些库可以用来帮你处理命令行参数, 而不必你自己来扫描`Sys.argv` 数组:
这里是一些与内置的 `Arg` 模块提供的工具不同或是更丰富的库:

* [Cmdliner](https://erratique.ch/software/cmdliner/doc/Cmdliner) 是一个用于命令行处理的现代化接口,
同时也能自动生成 UNIX man pages。

* [Clap](https://opam.ocaml.org/packages/clap/) 是一个命令式的命令行解析器。

* [Minicli](https://opam.ocaml.org/packages/minicli/) 对格式不正确的命令行有较好的抵抗能力(不接受),与之相比其它的可能会静默接受(不报错)。

- `Arg` 是标准库的一个模块。
- `OCaml版的Getopt` ,与[GNU
getopt](http://www.gnu.org/software/libc/manual/html_node/Getopt.html)类似。
- [Pa_arg](http://www-personal.umich.edu/~ebreck/code/pa_arg/)库提供了一个方便的语法来定义和识别选项。
* 用于 Ocaml 的 [Getopt](https://opam.ocaml.org/packages/getopt/) 与
[GNU getopt](http://www.gnu.org/software/libc/manual/html_node/Getopt.html) 类似。