我倾向于谈论命令行应用程序是多么简单。通常,我会说“它们从标准输入读取并写入标准输出和标准错误,就是这样。”然后,我画了一张图,如下所示:
但后来我开始认为这实际上并不是故事的结局。命令行应用程序从标准输入读取并写入标准错误和标准输出,以及...
- 读取配置文件(从任意数量的位置,如
/etc
、~
或.
) - 读取环境变量
- 读取命令行开关
- 读取命令行标志
- 读取命令行参数
- 设置退出状态
命令行应用程序最终看起来更像是:
客观上来说,这比简单的 stdin、stdout 和 stderr 更复杂。
我整天使用命令行应用程序。随着时间的推移,我对它们形成了自己的看法和直觉:
- 标准 - 通常是数据,而不是参数(除非我使用
xargs
) - 标准输出 - 产品,最好是列式数据
- 标准错误 - 用于在出现问题时记录错误或错误消息
- 退出状态 - 神圣,0 表示成功,1 表示失败(或者其他类型的失败)
- 参数 - 很重要,但阅读方式与标准不同
- 标志 - 类似于参数,但不太重要
- 开关 - 类似于标志,但要么打开要么关闭
- 环境变量 - 有点像参数,但它们之间存在某种哲学差异
- 配置文件 - 有点像环境变量,您可以控制开关或标志,但您可以将它们保留在版本控制中,并且通常使用它们来防止不得不使用开关和标志的令人沮丧的组合,或者向每个人展示您的点文件有多酷是
这些指南往往适用于我使用的各种命令行工具。不过,当谈到制作它们供其他人使用时,我希望有一个参考。
例如,以下是用 Ruby 编写的命令行应用程序:
#!/usr/bin/env ruby
# somecli
require 'optparse'
require 'yaml'
options = {}
etc_config = File.join('etc', 'somecli')
if File.exist? etc_config
options.merge! YAML.load_file(etc_config)
end
home_config = File.join(ENV['HOME'], '.somecli')
if File.exist? home_config
options.merge! YAML.load_file(home_config)
end
current_working_directory_config = '.somecli'
if File.exist? current_working_directory_config
options.merge! YAML.load_file(current_working_directory_config)
end
OptionParser.new do |opts|
opts.on("-s", "--[no-]switch") do |s|
options[:switch] = s
end
opts.on("-a", "--[no-]another-switch") do |as|
options[:'another-switch'] = as
end
opts.on("-y", "--[no-]yet-another-switch") do |yas|
options[:'yet-another-switch'] = yas
end
opts.on("-y", "--[no-]even-yet-another-switch") do |eyas|
options[:'even-yet-another-switch'] = eyas
end
opts.on("--flag FLAG") do |f|
options[:flag] = f
end
end.parse!
puts "ARGV=#{ARGV.inspect}"
puts "options=#{options.inspect}"
puts "ENV['cats']=#{ENV['cats'].inspect}"
unless STDIN.tty?
puts "STDIN.read=#{STDIN.read.inspect}"
end
$stderr.puts "stderr: hello world!"
$stdout.puts "stdout: hello world!"
exit 0
运行时看起来像
echo -n foo bar baz | ./somecli -s -f flap jacks; echo $?
ARGV=["jacks"]
options={:"another-switch"=>true, :"even-yet-another-switch"=>true, :switch=>true, :flag=>"flap"}
ENV['cats']="flapjacks"
STDIN.read="foo bar baz"
stderr: hello world!
stdout: hello world!
0
我想在自述文件中提供一个更正式的文档的链接,该文档指导其输入和输出约定以及设计决策。
我知道POSIX存在用于决定系统上安装哪些常见实用程序,除了在构建命令行应用程序时我想知道两件事:
- 还有其他我错过的将输入输入到命令行程序的常见方法吗?
- 是否存在有关如何组织各种输入和输出的约定的权威文档?
答案1
POSIX.1/Single UNIX 规范中记录了一些命令行实用程序的一般准则。
答案2
我认为除了常识之外没有任何约定。
至于输入输出方式,或者一般来说,沟通方法,您提到了一种方法,但只是部分方法。它正在读取和写入文件。 (您提到读取配置文件,或者一般来说,文件描述符.)
与文件类似的还有管道和套接字。例如。 TCP、UDP 或 Unix 套接字,这可以被认为是一种方式进程间通信(IPC)。其他类型的 IPC 包括:
- 共享内存和映射内存
- 信号
- 信号量
- D总线