比如下面这个程序,counter 函数将 count 的值+1 ,但程序最后的输出确还是 1 ,我觉得输出 2 才应该是正常的。
#!/bin/bash
count=1
counter()
{
count=$((count + 1))
echo test
}
str=$(counter)
echo $count
如果我调用 counter 的时候没有把输出保存在 str 里,那么程序最后的输出就是 2
1
zhlxsh 2022-07-20 00:33:06 +08:00 via iPhone
1. $() 的调用方式是开了一个子进程。子进程 count 的值是不会影响父进程的
2. 在函数里直接覆盖全局变量感觉有些不妥😂不知道别人都是怎么写的 |
2
foam 2022-07-20 01:14:47 +08:00 2
@zhlxsh #1
$() 其实没有 fork 子进程,只是一个 subshell ,可以用 `strace` 看下系统调用,只是 call 了 pipe(),并拷贝了 parent shell 的环境变量。因此 subshell 修改的只是它所在环境的值,无法影响到 parent shell @OP 如果期望修改 parent shell 的变量,那只能在同一个 shell 环境下去修改。希望返回值的话,可以传个参数进去,然后在函数里使用 printf -v 给该参数赋值 #!/bin/bash count=1 counter() { count=$((count + 1)) printf -v $1 test } counter ret echo $ret echo $count |
3
wxf666 2022-07-20 03:28:03 +08:00 3
原因 @zhlxsh #1 和 @foam #2 说了,解决方法 @foam #2 说了,我来扩展下思路
在通过『|』『()』『$()或``』启动的 subshell 中修改变量,只会在 subshell 中生效,不会影响 parent shell: ```bash declare -i total=0 sum() { for i in {1..3}; do total+=i done printf '%4s: %d\n' "$1" "$total" } sum '|' | cat (sum '()') echo "$(sum '$()')" echo "外部: $total" ``` 结果,三种方式启动的 subshell ,都计算得 total=1+2+3=6 ,但实际都未修改外部的 total: ``` |: 6 (): 6 $(): 6 外部: 0 ``` 若要修改,就要在同一个 shell 环境中。对于『|』,可以尽量用『<<<』『< <()』等代替: ```bash # seq 3 | while read -r i; do total+=i done < <(seq 3) ``` 如果要捕捉输出,就想办法赋值到某个变量中(如 @foam #2 利用的 printf -v )。但归根结底,还是利用了 bash 的『动态作用域』。 bash 手册说,变量对自身及调用的子函数可见 > variables are visible only to the function and the commands it invokes 函数中使用了某个变量,先在自身找,找不到则在上一层调用者中找,一直到全局作用域 > visible variables and their values are a result of the sequence of function calls that caused execution to reach the current function. The value of a variable that a function sees depends on its value within its caller, if any, whether that caller is the "global" scope or another shell function 1. 所以,简单地,可以直接约定,子函数输出到 out 变量中,调用者直接用 out 变量 ```bash count=1 counter() { (( count++ )) out='test' # 自身找不到 out ,就在调用者 main 中找到 out 再赋值 } main() { local out counter echo "count: $count, out: $out" } main # 输出:count: 2, out: test ``` 2. 将『要赋值到哪个变量』作为参数 /环境变量,传递给子函数,子函数自己想办法赋值 2.1 使用 @foam #2 说的 printf -v 2.2 使用『引用』 ```bash count=1 global_out= counter1() { # 本函数内,out 是『名为「$1 的值」的变量』的引用(可同名,外部作用域的同名变量暂时被隐藏) # 如,out 是 main_out 的引用。对 out 的操作,实际是对 main_out 操作(自身找不到 main_out ,就在 main 找) declare -n out=$1; shift (( count++ )) out='test1' } counter2() { # 本函数内,out 是『名为「$out 的值」的变量』的引用 # 右边的 out 是调用者临时扩充的环境变量,如 global_out (自身、main 找不到 global_out ,就在全局作用域找) declare -n out=$out (( count++ )) out='test2' } main() { local main_out counter1 main_out # 作为参数传递 out=global_out counter2 # 作为临时环境变量传递(综合觉得这种调用好看) echo "count: $count, main_out: $main_out, global_out: $global_out" } main # 输出:count: 3, main_out: test1, global_out: test2 ``` |
4
haoliang 2022-07-20 04:19:47 +08:00
@foam 你应该是忘了加 `--follow-forks` 给 strace, 所以没看到 child process 的系统调用,即便如此也有 clone 呀... 因此 subshell 是个 child process
|
5
zhlxsh 2022-07-20 09:31:51 +08:00 via iPhone
结合上面说的调用的时候有问题,可以通过重定向到一个文件,然后读取这个文件,貌似也可以。如:
counter > /tmp/counter; str=$(cat /tmp/counter) |
6
foam 2022-07-20 12:52:27 +08:00
@haoliang #4 cc @zhlxsh #1
谢谢指正,之前不知道 clone 也是一个创建子进程的 system call ,学习了。 Clone : Clone, as fork, creates a new process. Unlike fork, these calls allow the child process to share parts of its execution context with the calling process, such as the memory space, the table of file descriptors, and the table of signal handlers |