Command line parameters and environment variables/zh CN: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | 在大多数(交互式)操作系统中,可以通过[[Command-line interface|命令行界面]](CLI)启动[[Program|程序]],并允许向程序提供附带的数据。[[System unit|system]]单元(以及[[Object Pascal|`objPas`]])单元提供了一些基本的[[Function|函数]],用于访问这些通过命令行给出的数据。 | |
| 2 | 2 | ||
| 3 | 3 | ## Contents | |
| 4 | 4 | ||
| 5 | 5 | * 1 概述 | |
| 6 | 6 | * 1.1 术语 | |
| 7 | 7 | * 1.2 底层函数 | |
| 8 | 8 | * 2 命令行参数 | |
| 9 | 9 | * 2.1 基本用法 | |
| 10 | 10 | * 2.2 用户友好性 | |
| 11 | 11 | * 2.2.1 检测某个参数 | |
| 12 | 12 | * 2.2.2 读取参数值 | |
| 13 | 13 | * 2.2.3 校验参数 | |
| 14 | 14 | * 3 环境变量 | |
| 15 | 15 | ||
| 16 | 16 | ## 概述 | |
| 17 | 17 | ||
| 18 | 18 | 尽管这里介绍的是“命令行”参数和环境变量,但使用传给程序的数据,以及这里提及的例程和术语,都不需要实际用到命令行界面(CLI)。只是命令行界面更为直观,因此以下内容主要考虑 shell 用户的体验。 | |
| 19 | 19 | ||
| 20 | 20 | ### 术语 | |
| 21 | 21 | ||
| 22 | 22 | 选项(Option) | |
| 23 | 23 | 选项就是“是/否”。通常假定为如下形式 | |
| 24 | 24 | ||
| 25 | 25 | `‑‑dryrun`/`‑‑no‑dryrun` (分别为启用和禁用 dryrun 选项)。 选项也被称为“开关”。 | |
| 26 | 26 | ||
| 27 | 27 | 参数(Parameter) | |
| 28 | 28 | 参数是键值元组。在命令行中的形式如下: | |
| 29 | 29 | ||
| 30 | 30 | `‑‑processors 4`。 | |
| 31 | 31 | ||
| 32 | 32 | 实参(Argument) | |
| 33 | 33 | 实参就是一些简单的单词。在命令行中通常以空格分隔(某些 shell 中可由 IFS 变量定义)。从狭义上讲, _实参_ 指既非选项也不属于参数的所有单词。而从广义上讲,实参是指命令行中未以任何方式解释的所有单词。 | |
| 34 | 34 | ||
| 35 | 35 | 环境变量(Environment variable) | |
| 36 | 36 | 环境变量是指运行环境中打了标签的一块存储空间,都是“名称/值”对。 | |
| 37 | 37 | ||
| 38 | 38 | 请注意,命令行界面操作系统向程序传递的参数会先进行处理:比如转义后的换行符、文件重定向(管道或重定向文件)、对环境变量的赋值,都不会传给程序。 | |
| 39 | 39 | ||
| 40 | 40 | ### 底层函数 | |
| 41 | 41 | ||
| 42 | 42 | “选项”和“参数”的概念抽象程度已经较高,且存在不同的风格(如短选项和长选项),而 system 单元的目标是提供较为 _底层_ 的工作方式,因此只把全部命令行参数视为从零开始的枚举类型,未做任何解释处理。 | |
| 43 | 43 | ||
| 44 | 44 | 函数 [`paramStr`](<https://www.freepascal.org/docs-html/rtl/system/paramstr.html>) 用于返回第 n 个命令行参数。 `ParamStr(0)` 尝试返回可执行程序文件名,以及完整路径。 | |
| 45 | 45 | ||
| 46 | 46 | ## 命令行参数 | |
| 47 | 47 | ||
| 48 | 48 | ### 基本用法 | |
| 49 | 49 | ||
| 50 | 50 | Pascal 程序可以将 `paramStr` [[Function|函数]] 和 [`paramCount`](<https://www.freepascal.org/docs-html/rtl/system/paramcount.html>) 结合起来,以便访问命令行参数。 | |
| 51 | 51 | ||
| 52 | 52 | `ParamCount` 将返回给出的参数个数。 | |
| 53 | 53 | ``` | |
| 54 | 54 | program listArguments(input, output, stdErr); | |
| 55 | 55 | {$mode objFPC} | |
| 56 | 56 | var | |
| 57 | 57 | i: integer; | |
| 58 | 58 | begin | |
| 59 | 59 | writeLn({$ifDef Darwin} | |
| 60 | 60 | // Mac OS X 中的返回值依赖于调用方法 | |
| 61 | 61 | 'This program was invoked via: ', | |
| 62 | 62 | {$else} | |
| 63 | 63 | // 兼容 Turbo Pascal 的 paramStr(0) 返回程序所在位置 | |
| 64 | 64 | 'This program is/was stored at: ', | |
| 65 | 65 | {$endIf} | |
| 66 | 66 | paramStr(0)); | |
| 67 | 67 | ||
| 68 | 68 | for i := 1 to paramCount() do | |
| 69 | 69 | begin | |
| 70 | 70 | writeLn(i:2, '. argument: ', paramStr(i)); | |
| 71 | 71 | end; | |
| 72 | 72 | end. | |
| 73 | 73 | ``` | |
| 74 | 74 | ||
| 75 | 75 | 在非 Mac OS X 系统中,上述程序的运行结果可能如下: | |
| 76 | 76 | ``` | |
| 77 | 77 | $ ./listArguments foo bar able | |
| 78 | 78 | This program is/was stored at: /tmp/listArguments | |
| 79 | 79 | 1. argument: foo | |
| 80 | 80 | 2. argument: bar | |
| 81 | 81 | 3. argument: able | |
| 82 | 82 | ``` | |
| 83 | 83 | ||
| 84 | 84 | RTL 的 system 单元提供了一个 `paramStr`,返回的是[[Shortstring|`shortString`]] 类型的字符串。`shortString` 类型的实现方式限制了其长度在255个字符以内。 但是用户可能会给出长度超限的命令行参数。 为了访问全部命令行参数,[`objPas` 单元](<https://www.freepascal.org/docs-html/rtl/objpas/index.html>) 重新定义了 `paramStr`,返回类型换成了 [[Ansistring|`ansiString`]],这样长度就不受限了。在 [[Mode ObjFPC|`{$mode objFPC}`]](第2行)和 [[Mode Delphi|`{$mode Delphi}`]] 编译模式下,会自动包含 `objPas` 单元。 | |
| 85 | 85 | ||
| 86 | 86 | 提供 `paramStr` 函数是为了与 Turbo Pascal 保持兼容。在 Turbo Pascal 中,`paramStr(0)` 返回程序的所在位置。但这需要操作系统的支持。尤其值得注意的是,在 Mac OS X 系统中,`paramStr(0)` 的返回值取决于调用方法,即应用程序的启动方式。 | |
| 87 | 87 | ||
| 88 | 88 |  **Note:** 由于 `paramStr(0)` 依赖于操作系统的支持,所以并不适用于 [[Multiplatform Programming Guide|跨平台应用]]。 | |
| 89 | 89 | ||
| 90 | 90 |  **Note:** 在类 Unix 系统中,只用字符串不可能确定文件的位置。可能有多个硬链接指向同一个i节点,文件可能已删除,文件路径可能已有部分改动,以及其他细节问题。 | |
| 91 | 91 | ||
| 92 | 92 | 因此,在类 Unix 系统中 `paramStr(0)` 无效。 | |
| 93 | 93 | ||
| 94 | 94 | [`getOpts` 单元](<https://www.freepascal.org/docs-html/rtl/getopts/index.html>)提供了其他一些函数,在以特定格式调用程序时可用于解析命令行参数。 | |
| 95 | 95 | ||
| 96 | 96 | ### 用户友好性 | |
| 97 | 97 | ||
| 98 | 98 | 设计良好的程序在收到错误参数时应能给出帮助信息,并且应该遵循一种通用的参数传递方式。 FPC 的 [unit `custApp`](<https://www.freepascal.org/docs-html/fcl/custapp/index.html>) 单元提供了一个 [`TCustomApplication`](<https://www.freepascal.org/docs-html/fcl/custapp/tcustomapplication.html>) 类,可用于轻松检查和读取命令行参数。当然,用 `paramStr` 和 `paramCount` 直接访问也没问题。 | |
| 99 | 99 | ||
| 100 | 100 | [[LCL]] 应用程序都会自动使用 TCustomApplication。Application 对象就是 TCustomApplication 的实例。 | |
| 101 | 101 | ||
| 102 | 102 | 若要编写非 LCL 程序,那么先在 [[Lazarus]] 中新建“控制台应用程序”类型的项目。这会创建一个包含某些实用功能的 project1.lpr,这些功能几乎所有程序都需要用到。接下来,请定位到 `doRun` 方法。 | |
| 103 | 103 | ||
| 104 | 104 | #### 检测某个参数 | |
| 105 | 105 | ||
| 106 | 106 | TCustomApplication 可以通过名称访问命令行参数。 比如用户给出通用的求助参数 _-h_ 时,程序应该打印出一条帮助文字。 _-h_ 是个短选项。 长选项则为 _\--help_ 。 若要测试用户在调用程序时是否带有 _-h_ 或 _\--help_ 参数,可以使用如下代码: | |
| 107 | 107 | ``` | |
| 108 | 108 | if hasOption('h', 'help') then | |
| 109 | 109 | begin | |
| 110 | 110 | writeHelp; | |
| 111 | 111 | halt; | |
| 112 | 112 | end; | |
| 113 | 113 | ``` | |
| 114 | 114 | ||
| 115 | 115 |  **Note:** 在 LCL 窗体中,必须在 `hasOption` 前面加上 `application.`。 | |
| 116 | 116 | ||
| 117 | 117 | 例如: | |
| 118 | 118 | ``` | |
| 119 | 119 | if application.hasOption('h', 'help') then | |
| 120 | 120 | begin | |
| 121 | 121 | writeHelp; | |
| 122 | 122 | halt; | |
| 123 | 123 | end; | |
| 124 | 124 | ``` | |
| 125 | 125 | ||
| 126 | 126 | 如果只支持短选项,请用以下代码: | |
| 127 | 127 | ``` | |
| 128 | 128 | if hasOption('h', '') then | |
| 129 | 129 | ``` | |
| 130 | 130 | ||
| 131 | 131 | 若只支持长选项,则用: | |
| 132 | 132 | ``` | |
| 133 | 133 | if hasOption('help') then | |
| 134 | 134 | ``` | |
| 135 | 135 | ||
| 136 | 136 | #### 读取参数值 | |
| 137 | 137 | ||
| 138 | 138 | 每个命令行参数可以给定一个值。 例如: | |
| 139 | 139 | ``` | |
| 140 | 140 | $ project1 -f filename | |
| 141 | 141 | ``` | |
| 142 | 142 | ||
| 143 | 143 | 或者长格式: | |
| 144 | 144 | ``` | |
| 145 | 145 | $ project1 --file=filename | |
| 146 | 146 | ``` | |
| 147 | 147 | ||
| 148 | 148 | 读取 `filename` : | |
| 149 | 149 | ``` | |
| 150 | 150 | writeLn('f=', getOptionValue('f', 'file')); | |
| 151 | 151 | ``` | |
| 152 | 152 | ||
| 153 | 153 | 注意:如果报错“`Option at position 1 needs an argument : f.`”,则是在调用 | |
| 154 | 154 | ``` | |
| 155 | 155 | [checkOptions](<https://www.freepascal.org/docs-html/fcl/custapp/tcustomapplication.checkoptions.html>) 时忘记加入该选项了。 | |
| 156 | 156 | ``` | |
| 157 | 157 | ||
| 158 | 158 | #### 校验参数 | |
| 159 | 159 | ||
| 160 | 160 | 命令行参数就是一条内容随意的文本,因此用户输入很容易出错。 因此,必须对命令行参数作语法检查。用 [CheckOptions](<http://lazarus-ccr.sourceforge.net/docs/fcl/custapp/tcustomapplication.checkoptions.html> "doc:fcl/custapp/tcustomapplication.checkoptions.html") 方法即可完成检查。 | |
| 161 | 161 | ||
| 162 | 162 | 可定义允许哪些参数及哪些参数需要有值,在发生语法错误时可获取错误消息和出错的选项,以便打印出帮助信息和错误细节。 | |
| 163 | 163 | ||
| 164 | 164 | 示例: | |
| 165 | 165 | ``` | |
| 166 | 166 | errorMsg := checkOptions('hf:', 'help file:'); | |
| 167 | 167 | ``` | |
| 168 | 168 | ||
| 169 | 169 | 上述代码允许传递短选项 `‑f value` 和 `‑h`, 还允许 `‑‑help` 或 `‑‑file=filename`。 但不允许 `‑‑help` 带有值,也不允许 `‑‑file` 没有值。 | |
| 170 | 170 | ||
| 171 | 171 | 参数示例: | |
| 172 | 172 | ``` | |
| 173 | 173 | procedure TMainForm.FormShow(Sender: TObject); | |
| 174 | 174 | var | |
| 175 | 175 | I: Integer; | |
| 176 | 176 | Params : TStringList | |
| 177 | 177 | LongOpts : array [1..2] of string = ('debug-sync', 'config-dir:'); | |
| 178 | 178 | begin | |
| 179 | 179 | Params := TStringList.Create; | |
| 180 | 180 | try | |
| 181 | 181 | Application.GetNonOptions('hgo:', LongOpts, Params); | |
| 182 | 182 | for I := 0 to Params.Count -1 do | |
| 183 | 183 | debugln('Extra Param ' + inttostr(I) + ' is ' + Params[I]); } | |
| 184 | 184 | finally | |
| 185 | 185 | FreeAndNil(Params); | |
| 186 | 186 | end; | |
| 187 | 187 | end; | |
| 188 | 188 | ``` | |
| 189 | 189 | ||
| 190 | 190 | 以上例程会查找参数,且不会和命令行开关或选项混淆。 应用程序还接受 --debug-sync 和 --config-dir=somedir 开关,只是没有打印出来。 | |
| 191 | 191 | ||
| 192 | 192 | 请注意,Application.GetNonOptions() 方法以数组类型的长选项为参数,而 Application.CheckOptions 方法的参数数据相同却只认字符串。 有点遗憾! | |
| 193 | 193 | ||
| 194 | 194 | ## 环境变量 | |
| 195 | 195 | ||
| 196 | 196 | [[sysutils|`sysUtils`]] 单元定义了3个读取环境变量的基本函数。 | |
| 197 | 197 | ||
| 198 | 198 | * [`getEnvironmentVariableCount`](<https://www.freepascal.org/docs-html/rtl/sysutils/getenvironmentvariablecount.html>) 返回全部环境变量的数量。 | |
| 199 | 199 | * [`getEnvironmentString`](<https://www.freepascal.org/docs-html/rtl/sysutils/getenvironmentstring.html>) 按索引编号返回某个环境变量。 | |
| 200 | 200 | * [`getEnvironmentVariable`](<https://www.freepascal.org/docs-html/rtl/sysutils/getenvironmentvariable.html>) 按键值返回某个环境变量。 | |
| 201 | 201 | ||
| 202 | 202 | 示例: | |
| 203 | 203 | ``` | |
| 204 | 204 | program environmentVariablesList(input, output, stdErr); | |
| 205 | 205 | uses | |
| 206 | 206 | sysUtils; | |
| 207 | 207 | var | |
| 208 | 208 | i: integer; | |
| 209 | 209 | begin | |
| 210 | 210 | for i := 0 to getEnvironmentVariableCount() - 1 do | |
| 211 | 211 | begin | |
| 212 | 212 | writeLn(getEnvironmentString(i)); | |
| 213 | 213 | end; | |
| 214 | 214 | end. | |
| 215 | 215 | ``` | |
| 216 | 216 | ||
| 217 | 217 | 另外还有一种可能的方式,就是将所有环境变量载入 [`tStringList`](<https://www.freepascal.org/docs-html/rtl/classes/tstringlist.html>)对象,以便访问键值对。 字符串列表会自动处理分隔符,默认为[[=|等号 (`'='`)]]。 | |
| 218 | 218 | ``` | |
| 219 | 219 | program environmentVariablesSorted(input, output, stdErr); | |
| 220 | 220 | {$mode objFPC} | |
| 221 | 221 | uses | |
| 222 | 222 | classes, sysUtils; | |
| 223 | 223 | var | |
| 224 | 224 | i: integer; | |
| 225 | 225 | environmentVariables: tStringList; | |
| 226 | 226 | begin | |
| 227 | 227 | environmentVariables := tStringList.create(); | |
| 228 | 228 | ||
| 229 | 229 | try | |
| 230 | 230 | // load all variables to string list | |
| 231 | 231 | for i := 0 to getEnvironmentVariableCount() - 1 do | |
| 232 | 232 | begin | |
| 233 | 233 | environmentVariables.append(getEnvironmentString(i)); | |
| 234 | 234 | end; | |
| 235 | 235 | ||
| 236 | 236 | environmentVariables.sort; | |
| 237 | 237 | for i := 0 to environmentVariables.count - 1 do | |
| 238 | 238 | begin | |
| 239 | 239 | writeLn(environmentVariables.names[i]:20, | |
| 240 | 240 | ' = ', | |
| 241 | 241 | environmentVariables.valueFromIndex[i]); | |
| 242 | 242 | end; | |
| 243 | 243 | finally | |
| 244 | 244 | environmentVariables.free; | |
| 245 | 245 | end; | |
| 246 | 246 | end. | |
| 247 | 247 | ``` | |
| 248 | 248 | ||
| 249 | 249 | 还可以采用 `tCustomApplication` 的 [[Method|方法]],可一次性将全部环境变量读取为一个[[TStringList|`tStringList`]]。大致如下所示: | |
| 250 | 250 | ``` | |
| 251 | 251 | var | |
| 252 | 252 | environmentVariables: tStringList; | |
| 253 | 253 | begin | |
| 254 | 254 | environmentVariables := tStringList.create(); | |
| 255 | 255 | try | |
| 256 | 256 | application.getEnvironmentList(environmentVariables); | |
| 257 | 257 | … | |
| 258 | 258 | finally | |
| 259 | 259 | environmentVariables.free; | |
| 260 | 260 | end; | |
| 261 | 261 | end; | |
| 262 | 262 | ``` | |
| 263 | 263 | *[IFS]: internal field separator | |
| 264 | 264 | ||
| 265 | 265 | --- | |
| 266 | - | ||
| 266 | + | *From the Free Pascal wiki, page "Command line parameters and environment variables/zh CN", as the Internet Archive kept it on 2025-01-13, last changed on 2024-12-09. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
