geekdoc-python-zh/docs/realpython/python-ruby-and-golang-a-co...

12 KiB
Raw Permalink Blame History

Python、Ruby 和 Golang:命令行应用程序比较

原文:https://realpython.com/python-ruby-and-golang-a-command-line-application-comparison/

2014 年末,我开发了一个名为 pymr 的工具。我最近觉得有必要学习 golang 并更新我的 ruby 知识,所以我决定重温 pymr 的想法,并用多种语言构建它。在这篇文章中我将分解“Mr”(Merr)应用程序(pymrgomrrumr)并展示每种语言中特定部分的实现。我会在最后提供一个总体的个人偏好,但会把个人作品的比较留给你。

对于那些想直接跳到代码的人,请查看 repo

应用程序结构

这个应用程序的基本思想是您有一些相关的目录您希望在这些目录上执行一个命令。“mr”工具提供了一种注册目录的方法以及一种在已注册目录组上运行命令的方法。该应用程序具有以下组件:

  • 命令行界面
  • 注册命令(用给定的标签写一个文件)
  • 运行命令(在注册的目录上运行给定的命令)

Remove ads

命令行界面

“mr”工具的命令行界面是:

$ pymr --help
Usage: pymr [OPTIONS] COMMAND [ARGS]...

Options:
 --help  Show this message and exit.

Commands:
 register  register a directory
 run       run a given command in matching...

为了比较命令行界面的构建,我们来看看每种语言中的 register 命令。

Python (pymr)

为了用 python 构建命令行界面,我选择使用 click 包。

@pymr.command()
@click.option('--directory', '-d', default='./')
@click.option('--tag', '-t', multiple=True)
@click.option('--append', is_flag=True)
def register(directory, tag, append):
    ...

红宝石(鲁姆)

为了在 ruby 中构建命令行界面,我选择了使用 thor gem。

desc 'register', 'Register a directory'
method_option :directory,
              aliases: '-d',
              type: :string,
              default: './',
              desc: 'Directory to register'
method_option :tag,
              aliases: '-t',
              type: :array,
              default: 'default',
              desc: 'Tag/s to register'
method_option :append,
              type: :boolean,
              desc: 'Append given tags to any existing tags?'
def register
  ...

戈朗(gomr)

为了在 Golang 中构建命令行界面,我选择使用 cli.go 包。

app.Commands  =  []cli.Command{ { Name:  "register", Usage:  "register a directory", Action:  register, Flags:  []cli.Flag{ cli.StringFlag{ Name:  "directory, d", Value:  "./", Usage:  "directory to tag", }, cli.StringFlag{ Name:  "tag, t", Value:  "default", Usage:  "tag to add for directory", }, cli.BoolFlag{ Name:  "append", Usage:  "append the tag to an existing registered directory", }, }, }, }

注册

注册逻辑如下:

  1. 如果用户要求--append读取存在的.[py|ru|go]mr文件。
  2. 将现有标签与给定标签合并。
  3. 用新标签写一个新的.[...]mr文件。

这可以分解成几个小任务,我们可以用每种语言进行比较:

  • 搜索和读取文件。
  • 合并两个项目(仅保留唯一的集合)
  • 编写文件

文件搜索

Python (pymr)

对于 python 来说,这涉及到 os 模块。

pymr_file = os.path.join(directory, '.pymr')
if os.path.exists(pymr_file):
    # ...

红宝石(鲁姆)

对于 ruby 来说,这涉及到了文件类。

rumr_file = File.join(directory, '.rumr')
if File.exist?(rumr_file)
    # ...

戈朗(gomr)

对于 golang这涉及到路径包。

fn  :=  path.Join(directory,  ".gomr") if  _,  err  :=  os.Stat(fn);  err  ==  nil  { // ... }

Remove ads

唯一合并

Python (pymr)

对于 python这涉及到使用一个集合

# new_tags and cur_tags are tuples
new_tags = tuple(set(new_tags + cur_tags))

红宝石(鲁姆)

对于 ruby 来说,这涉及到的使用。uniq 数组方法。

# Edited (5/31)
# old method:
#  new_tags = (new_tags + cur_tags).uniq

# new_tags and cur_tags are arrays
new_tags |= cur_tags

戈朗(gomr)

对于 golang这涉及到自定义函数的使用。

func  AppendIfMissing(slice  []string,  i  string)  []string  { for  _,  ele  :=  range  slice  { if  ele  ==  i  { return  slice } } return  append(slice,  i) } for  _,  tag  :=  range  strings.Split(curTags,  ",")  { newTags  =  AppendIfMissing(newTags,  tag) }

文件读/写

我试图选择每种语言中最简单的文件格式。

Python (pymr)

对于 python 来说,这涉及到使用 pickle模块

# read
cur_tags = pickle.load(open(pymr_file))

# write
pickle.dump(new_tags, open(pymr_file, 'wb'))

红宝石(鲁姆)

对于 ruby 来说,这涉及到使用 YAML 模块。

# read
cur_tags = YAML.load_file(rumr_file)

# write
# Edited (5/31)
# old method:
#  File.open(rumr_file, 'w') { |f| f.write new_tags.to_yaml }
IO.write(rumr_file, new_tags.to_yaml)

戈朗(gomr)

对于 golang这涉及到使用配置包。

// read cfg,  _  :=  config.ReadDefault(".gomr") // write outCfg.WriteFile(fn,  0644,  "gomr configuration file")

Remove ads

运行(命令执行)

运行逻辑如下:

  1. 递归地从给定的基本路径开始搜索.[...]mr文件
  2. 加载一个找到的文件,看看给定的标签是否在其中
  3. 在匹配文件的目录中调用给定的命令。

这可以分解成几个小任务,我们可以用每种语言进行比较:

  • 递归目录搜索
  • 字符串比较
  • 调用 Shell 命令

递归目录搜索

Python (pymr)

对于 python 来说,这涉及到 os 模块和 fnmatch 模块。

for root, _, fns in os.walk(basepath):
    for fn in fnmatch.filter(fns, '.pymr'):
        # ...

红宝石(鲁姆)

对于 ruby这涉及到查找文件类。

# Edited (5/31)
# old method:
#  Find.find(basepath) do |path|
#        next unless File.basename(path) == '.rumr'
Dir[File.join(options[:basepath], '**/.rumr')].each do |path|
    # ...

戈朗(gomr)

对于 golang这需要 filepath 包和一个自定义回调函数。

func  RunGomr(ctx  *cli.Context)  filepath.WalkFunc  { return  func(path  string,  f  os.FileInfo,  err  error)  error  { // ... if  strings.Contains(path,  ".gomr")  { // ... } } } filepath.Walk(root,  RunGomr(ctx))

字符串比较

Python (pymr)

对于这个任务python 中不需要任何额外的东西。

if tag in cur_tags:
    # ...

红宝石(鲁姆)

在 ruby 中,这个任务不需要额外的东西。

if cur_tags.include? tag
    # ...

戈朗(gomr)

对于 golang这需要字符串包。

if  strings.Contains(cur_tags,  tag)  { // ... }

Remove ads

调用外壳命令

Python (pymr)

对于 python这需要 os 模块和子流程模块。

os.chdir(root)
subprocess.call(command, shell=True)

红宝石(鲁姆)

对于 ruby这涉及到内核模块和反勾号语法。

# Edited (5/31)
# old method
#  puts `bash -c "cd #{base_path} && #{command}"`
Dir.chdir(File.dirname(path)) { puts `#{command}` }

戈朗(gomr)

对于 golang 来说,这涉及到 os 包和 os/exec 包。

os.Chdir(filepath.Dir(path)) cmd  :=  exec.Command("bash",  "-c",  command) stdout,  err  :=  cmd.Output()

包装

该工具的理想分发模式是通过一个包。然后用户可以安装它tool install [pymr,rumr,gomr],并在系统路径上执行一个新命令。我不想在这里介绍打包系统,我只想展示每种语言所需的基本配置文件。

Python (pymr)

对于 python 来说,需要一个setup.py。一旦创建并上传了包,就可以用pip install pymr进行安装。

from setuptools import setup, find_packages

classifiers = [
    'Environment :: Console',
    'Operating System :: OS Independent',
    'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
    'Intended Audience :: Developers',
    'Programming Language :: Python',
    'Programming Language :: Python :: 2',
    'Programming Language :: Python :: 2.7'
]

setuptools_kwargs = {
    'install_requires': [
        'click>=4,<5'
    ],
    'entry_points': {
        'console_scripts': [
            'pymr = pymr.pymr:pymr',
        ]
    }
}

setup(
    name='pymr',
    description='A tool for executing ANY command in a set of tagged directories.',
    author='Kyle W Purdon',
    author_email='kylepurdon@gmail.com',
    url='https://github.com/kpurdon/pymr',
    download_url='https://github.com/kpurdon/pymr',
    version='2.0.1',
    packages=find_packages(),
    classifiers=classifiers,
    **setuptools_kwargs
)

红宝石(鲁姆)

对于 ruby需要一个rumr.gemspec。一旦宝石被创建并上传,就可以安装gem install rumr

Gem::Specification.new do |s|
  s.name        = 'rumr'
  s.version     = '1.0.0'
  s.summary     = 'Run system commands in sets' \
                  ' of registered and tagged directories.'
  s.description = '[Ru]by [m]ulti-[r]epository Tool'
  s.authors     = ['Kyle W. Purdon']
  s.email       = 'kylepurdon@gmail.com'
  s.files       = ['lib/rumr.rb']
  s.homepage    = 'https://github.com/kpurdon/rumr'
  s.license     = 'GPLv3'
  s.executables << 'rumr'
  s.add_dependency('thor', ['~>0.19.1'])
end

戈朗(gomr)

对于 golang 来说,源代码只是被编译成可以重新分发的二进制文件。不需要额外的文件,当前也没有要推送的包存储库。

Remove ads

结论

对于这个工具Golang 感觉是个错误的选择。我不需要它有很高的性能,我也没有利用 Golang 提供的本地并发性。这就给我留下了 Ruby 和 Python。对于大约 80%的逻辑,我个人的偏好是两者之间的一个掷硬币。以下是我觉得用一种语言写更好的作品:

命令行接口声明

Python 是这里的赢家。 click 库装饰风格声明简洁明了。请记住,我只尝试了红宝石雷神宝石,所以红宝石可能有更好的解决方案。这也不是对任何一种语言的评论,而是我在 python 中使用的 CLI 库是我的首选。

递归目录搜索

鲁比是这里的赢家。我发现使用 ruby 的Find.find()尤其是next unless语法,这一整段代码更加清晰易读。

包装

鲁比是这里的赢家。文件要简单得多,建造和推销宝石的过程也简单得多。捆绑器工具也使得在半隔离环境中安装变得轻而易举。

最终决定

由于打包和递归目录搜索偏好,我会选择 Ruby 作为这个应用程序的工具。然而,偏好上的差异是如此之小,以至于 Python 也非常适合。然而Golang 并不是这里的正确工具。

这篇文章最初发表在凯尔的个人博客上,并在 Reddit 上引起了热烈的讨论。*****