91

我们开发人员编写的大多数应用程序都需要在启动时进行外部参数化。我们传递文件路径、管道名称、TCP/IP 地址等。到目前为止,我一直在使用命令行将这些传递给正在启动的应用程序。我必须解析命令行main并将参数定向到需要它们的位置,这当然是一个很好的设计,但是对于大量参数来说很难维护。最近我决定使用环境变量机制。它们是全局的,可以从任何地方访问,从架构的角度来看不太优雅,但限制了代码量

这是我对这两种策略的第一印象(可能是相当浅薄的),但我想听听更有经验的开发人员的意见——使用环境变量和命令行参数将参数传递给进程有哪些优缺点?我想考虑以下事项:

  1. 设计质量(灵活性/可维护性),
  2. 内存限制,
  3. 解决方案的可移植性。

评论:

广告。1.这是我感兴趣的主要方面。

广告。2. 这有点务实。我知道 Windows 上的一些限制目前很大(命令行和环境块都超过 32kB)。不过我想这不是问题,因为如果需要,您应该使用文件来传递大量参数。

广告。3. 我对 Unix 几乎一无所知,所以我不确定这两种策略是否像在 Windows 上一样可用。请详细说明。

4

4 回答 4

85

1)我建议尽可能避免环境变量。

环境变量的优点

  • 易于使用,因为它们在任何地方都可见。如果很多独立的程序需要一条信息,这种方法就方便多了。

环境变量的缺点

  • 很难正确使用,因为它们在任何地方都可见(可删除、可设置)。如果我安装一个依赖于环境变量的新程序,它们会踩踏我现有的程序吗?我昨天在胡闹的时候是否无意中搞砸了我的环境变量?

我的意见

  • 对程序的每个单独调用最有可能不同的参数使用命令行参数(即,n 表示计算 n 的程序!)
  • 将配置文件用于用户可能合理想要更改但不经常更改的参数(即窗口弹出时的显示大小)
  • 谨慎使用环境变量——最好只用于预期不会改变的参数(即 Python 解释器的位置)
  • 你的观点They are global and accessible from anywhere, which is less elegant from architectural point of view, but limits the amount of code让我想起了使用全局变量的理由;)

亲身经历环境变量过度使用的恐怖给我留下的伤疤

  • 我们工作需要的两个程序,由于环境冲突,不能同时在同一台电脑上运行
  • 具有相同名称但存在不同错误的多个版本的程序——因为程序的位置是从环境中提取的,并且(默默地、巧妙地)错误地导致整个车间瘫痪数小时。

2) 限制

如果我要突破命令行可以容纳的限制,或者环境可以处理的限制,我会立即重构。

我过去曾将 JSON 用于需要大量参数的命令行应用程序。能够使用字典和列表以及字符串和数字非常方便。该应用程序只使用了几个命令行参数,其中一个是 JSON 文件的位置。

这种方法的优点

  • 不必编写很多(痛苦的)代码来与 CLI 库进行交互——让许多通用库来强制执行复杂的约束可能会很痛苦(我所说的“复杂”是指比检查更复杂)特定键或一组键之间的交替)
  • 不必担心 CLI 库对参数顺序的要求——只需使用 JSON 对象!
  • 易于表示复杂的数据(回答What won't fit into command line parameters?),例如列表
  • 易于使用来自其他应用程序的数据——以编程方式创建和解析
  • 易于适应未来的扩展

注意:我想将此与 .config-file 方法区分开来——这不是用于存储用户配置。也许我应该将此称为“命令行参数文件”方法,因为我将它用于需要大量不适合命令行的值的程序。


3) 解决方案可移植性:我不太了解 Mac、PC 和 Linux 在环境变量和命令行参数方面的区别,但我可以告诉你:

  • 这三个都支持环境变量
  • 它们都支持命令行参数

是的,我知道——这不是很有帮助。对不起。但关键是您可以期望一个合理的解决方案是可移植的,尽管您肯定想为您的程序验证这一点(例如,命令行参数在任何平台上是否区分大小写?在所有平台上?我不知道)。


最后一点:

正如 Tomasz 所提到的,对于参数来自的大多数应用程序来说,这应该无关紧要。

于 2011-09-28T19:06:36.637 回答
8

您应该使用策略模式抽象读取参数。使用以下实现创建一个名为ConfigurationSource具有readConfig(key) -> value方法(或返回一些Configuration对象/结构)的抽象:

  • CommandLineConfigurationSource
  • EnvironmentVariableConfigurationSource
  • WindowsFileConfigurationSource- 从配置文件加载C:/Document and settings...
  • WindowsRegistryConfigurationSource
  • NetworkConfigrationSource
  • UnixFileConfigurationSource- - 从配置文件加载/home/user/...
  • DefaultConfigurationSource- 默认值
  • ...

您还可以使用责任链模式来链接各种配置中的源,例如:如果未提供命令行参数,请尝试环境变量,如果其他一切都失败,则返回默认值。

广告 1. 这种方式不仅可以让你抽象读取配置,而且你可以很容易地改变底层机制,而不会对客户端代码产生任何影响。您也可以一次使用多个来源,回退或从不同来源收集配置。

广告 2. 只需选择适合的实施方式。当然,某些配置条目不适合例如命令行参数。

广告 3. 如果某些实现不可移植,则有两个,一个在不适合给定系统时静默忽略/跳过。

于 2011-09-16T10:50:50.907 回答
6

我认为这个问题已经得到了很好的回答,但我觉得它值得 2018 年更新。我觉得环境变量的一个未提及的好处是它们通常需要更少的样板代码来使用。这使得代码更清晰更易读。然而,一个主要的缺点是它们消除了在同一台机器上运行的不同应用程序的隔离层。我认为这就是 Docker 真正闪耀的地方。我最喜欢的设计模式是专门使用环境变量并在 Docker 容器内运行应用程序。这消除了隔离问题。

于 2018-05-09T17:22:05.113 回答
1

我一般同意之前的答案,但还有另一个重要方面:可用性

例如,git您可以在其中创建一个带有.git目录的存储库。要指定它,您可以使用命令行参数--git-dir或环境变量GIT_DIR

当然,如果您将当前目录更改为另一个存储库或在脚本中继承环境变量,则会出错。但是,如果您需要git在一个终端会话中在分离的存储库中键入多个命令,这非常方便:您不需要重复git-dir参数。

另一个例子是GIT_AUTHOR_NAME。似乎它甚至没有命令行伙伴(但是,git commit有一个--author参数)。GIT_AUTHOR_NAME覆盖user.nameauthor.name配置设置。

一般来说,命令行或环境参数的使用在 UNIX 上同样简单:可以使用命令行参数

$ command --arg=myarg

或一行中的环境变量:

$ ARG=myarg command

捕获命令行参数也很容易alias

alias cfg='git --git-dir=$HOME/.cfg/ --work-tree=$HOME'  # for dotfiles
alias grep='grep --color=auto'

通常,大多数参数都是通过命令行传递的。我同意之前的答案,即这更实用、更直接,并且脚本中的环境变量就像程序中的全局变量。

GNU libc是这样说的:

argv机制通常用于传递特定于正在调用的特定程序的命令行参数。另一方面,环境跟踪由许多程序共享、不经常更改且不经常使用的信息。

除了所说的环境变量的危险之外,它们还有很好的用例。GNU make对环境变量的处理非常灵活(因此与 shell 非常集成):

make在启动时看到的每个环境变量都会转换为具有相同名称和值的make变量。但是,makefile 中的显式分配或使用命令参数会覆盖环境。(- 并且有一个选项可以改变这种行为)......

因此,通过在您的环境中设置变量 CFLAGS,您可以使大多数 makefile 中的所有 C 编译都使用您喜欢的编译器开关。这对于具有标准或常规含义的变量是安全的,因为您知道没有任何 makefile 会将它们用于其他用途。

最后,我要强调的是,对于一个程序来说,最重要的不是程序员,而是用户体验。也许您将其包含在设计方面,但内部和外部设计是完全不同的实体。

以及关于编程方面的几句话。您没有编写您使用的语言,但让我们想象一下您的工具允许您进行最佳的参数解析。在 Python 中,我使用argparse,它非常灵活和丰富。要获取解析的参数,可以使用类似的命令

args = parser.parse_args()

args可以进一步拆分为已解析的参数(例如args.my_option),但我也可以将它们作为一个整体传递给我的函数。这个解决方案绝对不是“难以维护大量参数”(如果您的语言允许的话)。实际上,如果您有许多参数并且在参数解析期间未使用它们,请将它们在容器中传递到它们的最终目的地并避免代码重复(这会导致不灵活)。

最后的评论是解析环境变量比命令行参数容易得多。环境变量只是一对,VARIABLE=value. 命令行参数可能要复杂得多:它们可以是位置参数或关键字参数,或子命令(如git push)。它们可以捕获零个或多个值(回想一下命令echo和标志,如-vvv)。有关更多示例,请参见argparse

还有一件事情。你对记忆的担心有点令人不安。不要编写过于笼统的程序。一个库应该是灵活的,但是一个好的程序在没有任何参数的情况下是有用的。如果你需要传递很多,这可能是data,而不是arguments。如何将数据读入程序是一个更普遍的问题,没有适用于所有情况的单一解决方案。

于 2021-02-03T08:49:48.893 回答