leafee98-blog/content/posts/为单一命令设置代理环境变量的终端函数.md

114 lines
5.2 KiB
Markdown
Raw Normal View History

2022-04-28 06:40:02 +00:00
---
title: "为单一命令设置环境变量的终端函数"
date: 2021-04-19T14:10:41+08:00
lastmod: 2022-11-17T16:36:00+08:00
tags: [ linux, shell, bash ]
categories: [ tech ]
weight: 50
show_comments: true
2022-04-28 06:40:02 +00:00
draft: false
---
## 实际应用
### 需求
- 一个命令行函数
- 将其参数作为命令运行
- 为运行的命令设置环境变量, 同时不影响原本的 shell 环境变量
### 结果
```
function proxyenv {
(
proxy_dest="http://localhost:8888"
proxy=${proxy_dest}
http_proxy=${proxy_dest}
https_proxy=${proxy_dest}
PROXY=${proxy_dest}
HTTP_PROXY=${proxy_dest}
HTTPS_PROXY=${proxy_dest}
export proxy http_proxy https_proxy PROXY HTTP_RPOXY HTTPS_PROXY
eval "$@"
)
}
```
### 针对 sudo 命令
在使用 `sudo` 命令时,它的默认行为是不保留任何当前 shell 下的环境变量,因此即便使用 `proxyenv sudo wget` 命令,也无法在 `wget` 中使用这些环境变量。一个临时的解决方案是使用 `sudo``--preserve-env` 参数,像下面这样
```
$ proxyenv sudo -E wget http://example.org
```
如果希望简化每次需要敲的命令,可以在 `/etc/sudoer` 中添加以下内容,如此便可以每次默认保留这几个环境变量,`proxyenv sudo wget` 也会保留这些环境变量。
```
Defaults env_keep += "PROXY HTTP_PROXY HTTPS_PROXY proxy http_proxy https_proxy"
```
2022-04-28 06:40:02 +00:00
### 分析
使用圆括号创建 subshell 来隔离原来的变量环境, 在内部创建变量或 export 不会影响外部 shell 的变量.
使用 `eval "$@"` 将传入的参数作为命令执行. 由于 `$@` 的特性会将每一个参数展开为一个使用双引号的字符串, 以及 bash 会自动将双引号包裹的字符串作为一整个字符串并处理掉双引号, 所以不必担心在在调用时使用了双引号而导致变量的位置或者数量产生意料之外的行为(见参考 2).
## 相关知识
### subshell 和 export
使用括号包裹的命令会被置于一个 subshell 中运行, 即为这些命令单独开一个子进程并同时应运行 shell 来执行这些命令, 子进程中的 shell 称为 subshell. subshell 中绝大多数的环境变量等均与原 shell 相同, 即便有些没有被 export 的变量也能够在 subshell 中获取(见参考 3).
在 shell 中的变量如果不经过 export, 那么这些变量仅在当前的 shell 以及其 subshell 中可以获取, 在其子进程中则无法获取.
对于 subshell 和普通子进程的环境变量的区别可以见如下:
```
v="hello shell"
( echo $v )
bash -c 'echo $v'
export v
( echo $v )
bash -c 'echo $v'
```
上面的输出为:
```
hello shell
hello shell
hello shell
```
可以看出, 尚未被 export 的变量可以在 subshell 中获取, 但是子进程(这里为新开的 bash)对于这个变量一无所知, 所以第二行输出一个空行; 而已经 export 的变量可以 subshell 和子进程中获取.
### 特殊变量 * 和 @
> `*`
>
> Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable. If IFS is unset, the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening separators.
>
> `@`
>
> Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$@" is equivalent to "$1" "$2" ... If the double-quoted expansion occurs within a word, the expansion of the first parameter is joined with the beginning part of the original word, and the expansion of the last parameter is joined with the last part of the original word. When there are no positional parameters, "$@" and $@ expand to nothing (i.e., they are removed).
`$*` 变量会被展开为一个字符串, 不同的参数之间会使用 IFS 变量的第一个字符作为分隔符, 通常情况下是空格, 所以通常的效果就是一个使用空格分隔多个单词的字符串.
`$@` 变量会被展开为多个字符串, 不同的参数会成为各自独立的字符串, 若变量展开在一个字符串中, 那么展开后的第一个字符串会加入到原字符串的结尾, 最后一个字符串会加入到原字符串的开头, 比如 "start$@end" 会成为 "start$1" "$2" ... "$n-1" "$nend"
## 参考
1. [What is the best way to write a wrapper function that runs commands and logs their exit code](https://stackoverflow.com/questions/372116/what-is-the-best-way-to-write-a-wrapper-function-that-runs-commands-and-logs-the/372120#372120)
2. [Bash script - variable content as a command to run](https://stackoverflow.com/questions/5998066/bash-script-variable-content-as-a-command-to-run/39458935#39458935)
3. [Do parentheses really put the command in a subshell?](https://unix.stackexchange.com/questions/138463/do-parentheses-really-put-the-command-in-a-subshell/138498#138498)
4. [sudoer(5) - Linux manual page](https://man7.org/linux/man-pages/man5/sudoers.5.html)