参数传递策略 - 环境变量与命令行
Posted
技术标签:
【中文标题】参数传递策略 - 环境变量与命令行【英文标题】:Argument passing strategy - environment variables vs. command line 【发布时间】:2011-11-18 14:12:54 【问题描述】:我们开发人员编写的大多数应用程序都需要在启动时进行外部参数化。我们传递文件路径、管道名称、TCP/IP 地址等。到目前为止,我一直在使用 命令行 将这些传递给正在启动的应用程序。我必须解析main
中的命令行并将参数指向需要它们的位置,这当然是一个好的设计,但很难维护大量的论据。最近我决定使用环境变量机制。它们是全局的并且可以从任何地方访问,从架构的角度来看,这不太优雅,但限制了代码量。
这是我对这两种策略的第一印象(可能也很浅薄),但我想听听更有经验的开发人员的意见——使用环境变量和命令行参数传递的优缺点是什么流程的参数?我想考虑以下事项:
-
设计质量(灵活性/可维护性),
内存限制,
解决方案的可移植性。
备注:
广告。 1.这是我感兴趣的主要方面。
广告。 2. 这有点务实。我知道 Windows 上的一些限制,目前是huge(命令行和环境块都超过 32kB)。我想这不是问题,因为如果需要,您应该使用文件来传递大量参数。
广告。 3. 我对 Unix 几乎一无所知,所以我不确定这两种策略是否像在 Windows 上一样可用。请详细说明。
【问题讨论】:
您能否提供更多细节,例如参数的实际数量?如果他们有分组,或者他们都是随机的?这是什么语言? java、c++ 等...我要求详细程度的原因是,虽然用任何语言处理它都可能是一个问题,但可能存在您不知道的特定于语言实现的解决方案。 只是提到 *nix 操作系统,它们没有像“全局环境变量”这样的东西,并且每个 env var 在分叉时间从父进程继承到子进程。因此,“全局”不是命令行环境变量的专家,至少对于那些操作系统而言。 嗨,@jamesDrinkard。我对一般方法感兴趣。如果你想将 20 个不同的标记字符串/整数/实数参数从 32 位解释器运行的 Python 脚本传递到用 C++ 编写的 64 位应用程序,你会使用什么方法? 嗨,@shr。感谢您的 *nix 说明。正如雷蒙德在下面指出的那样,对于这项任务来说,这样的全局性根本不适合。 这可能是相关的并且提倡环境变量:devcenter.heroku.com/articles/config-vars 【参考方案1】:我一般同意之前的回答,但还有另一个重要方面:可用性。
例如,在git
中,您可以创建一个带有 .git 目录的存储库。要指定它,您可以使用命令行参数--git-dir
或环境变量GIT_DIR
。
当然,如果您将当前目录更改为另一个存储库或在脚本中继承环境变量,则会出错。但是,如果您需要在一个终端会话中在分离的存储库中键入多个 git
命令,这将非常方便:您无需重复 git-dir
参数。
另一个例子是GIT_AUTHOR_NAME
。似乎它甚至没有命令行伙伴(但是,git commit
有一个--author
参数)。 GIT_AUTHOR_NAME
覆盖 user.name 和 author.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。如何将数据读入程序是一个更为普遍的问题,没有适用于所有情况的单一解决方案。
【讨论】:
【参考方案2】:我认为这个问题已经得到了很好的回答,但我觉得它值得在 2018 年更新。我觉得环境变量的一个未提及的好处是它们通常需要更少的样板代码来使用。这使得代码更清晰更易读。然而,一个主要的缺点是它们消除了在同一台机器上运行的不同应用程序的隔离层。我认为这就是 Docker 真正闪耀的地方。我最喜欢的设计模式是专门使用环境变量并在 Docker 容器内运行应用程序。这消除了隔离问题。
【讨论】:
【参考方案3】: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 所提到的,对于大多数应用程序的参数来源而言,它应该无关紧要。
【讨论】:
谢谢你,马特。这是我一直在寻找的一种意见。您最重要的建议是使用环境变量来描述执行环境,这几乎不会改变,使用 cmd-file 来传递实际执行的简单/复杂参数。很理性,谢谢。请注意,您可以使用“本地”环境变量,这只会弄乱子进程。它与命令行参数传递非常相似,除了 Raymond 在 Tomasz 的回答下指出的。 非常好的答案!关于可以从任何地方更改环境变量的缺点:还可以从应用程序的启动脚本(例如 Bash 或 Batch 脚本)在本地设置环境变量。在这种情况下,可以有一个系统范围的默认值,但如果需要,应用程序可以将默认值更改为自定义值。您对此有何看法? 在考虑如何传递秘密/凭证时有什么优缺点吗? 我同意桌面和 CLI 应用程序。对于有许多部署的云系统,环境变量是一个很好的选择,例如在 12factor 指南中推荐:12factor.net/config【参考方案4】:您应该使用Strategy 模式抽象读取参数。创建一个名为ConfigurationSource
的抽象,具有readConfig(key) -> value
方法(或返回一些Configuration
对象/结构),实现如下:
CommandLineConfigurationSource
EnvironmentVariableConfigurationSource
WindowsFileConfigurationSource
- 从C:/Document and settings...
的配置文件加载
WindowsRegistryConfigurationSource
NetworkConfigrationSource
UnixFileConfigurationSource
- - 从/home/user/...
的配置文件加载
DefaultConfigurationSource
- 默认值
...
您还可以使用Chain of responsibility 模式在各种配置中链接源,例如:如果未提供命令行参数,请尝试环境变量,如果其他所有操作都失败,则返回默认值。
Ad 1. 这种方法不仅可以让您抽象读取配置,而且您可以轻松更改底层机制,而不会对客户端代码产生任何影响。您还可以同时使用多个来源,回退或从不同来源收集配置。
广告 2。只需选择合适的实施方式即可。当然,某些配置条目不适合例如命令行参数。
广告 3。如果某些实现不可移植,则有两个,一个在不适合给定系统时被忽略/跳过。
【讨论】:
谢谢,这通常是个好主意。但它对决定是使用环境还是命令行没有帮助。详细说明 Ad.2. 的 “某些配置条目不适合例如命令行参数” 会有所帮助。什么不适合字符串?如果它不适合,它可能应该在某种文件中间接传递,不是吗? 我的观点是:不要强迫用户使用命令行参数或环境变量。灵活(但保留可维护代码)。我相信配置文件是存储配置的最佳位置(它可以任意长,包含 cmets 等),但有时使用命令行参数覆盖文件配置很有用。什么不适合命令行参数?如果您需要传递多个文件路径,它可能会起作用,但没有人喜欢过长的命令行。 配置文件最适合参数 -- 这是宝贵的意见,对 cmets 的支持是使用它的好理由,谢谢。如果您在从批处理脚本启动应用程序时使用环境变量,您可以使用rem
和set
获得非常易读的表单。如果您正在生成一个进程,那么您只需 setenv
在 spawnl
-ing 之前您想要的。它方便、易读且灵活。为什么要使用 .config 而不是环境? 这就是问题。
注意环境变量是继承的。假设您的程序有两个参数ACTION
和一个可选的NOTIFY
。程序 A 设置 ACTION=if owner=nobody set owner=bob
和 NOTIFY=send
然后运行您的程序。您的程序更新一个项目,然后看到 NOTIFY
已设置并运行 send
。 send
程序向 Bob 发送电子邮件,然后再次运行您的程序,设置为 ACTION=set last_send = today
。它不想要任何通知,所以它不设置NOTIFY
。但它继承 NOTIFY
从程序 A,因此您的程序将上次运行更新到今天,然后运行 send
。无限循环。
谢谢你,@Raymond。环境变量的范围非常广泛。好点子。以上是关于参数传递策略 - 环境变量与命令行的主要内容,如果未能解决你的问题,请参考以下文章
Jmeter在非GUI环境下传递参数(命令行&Jenkins配置)
Shell特殊变量:$0, $#, $*, $@, $?, $$和命令行参数
Shell特殊变量 $0, $#, $*, $@, $?, $$和命令行参数