20.29 "while read line"循环内无法改变循环外变量 https://scz.617.cn/unix/201509231351.txt Q: -------------------------------------------------------------------------- #!/bin/bash count=0 # # "IFS="意在保持每行首尾两端的空白字符 # # 参看read(1),-r表示将反斜杠\视为普通字符 # ls -1aF /tmp | while IFS= read -r line do # # let count+=1 # count=$((count+1)) # ((count++)) # count=$(expr $count + 1 ) # # 第一种可移植性最高,最后一种效率最低 # count=$((count+1)) echo $count done printf "Found %u entries\n" "${count}" -------------------------------------------------------------------------- $ ./test.sh 1 2 ... 49 50 Found 0 entries 为什么最后显示0,而不是50? A: scz 2015-09-23 13:51 对于bash下的shell script,管道符|会导致后续命令在子shell里运行,此时 "while read line"循环内的count改变发生在子进程里,当然不会影响父进程(当前 shell)里的count。要想在父子进程间同步count,必将涉及进程间通信。不是所有的 shell对管道符的处理都是创建子shell,比如ksh就不是。 解决方案之一: -------------------------------------------------------------------------- #!/bin/bash count=0 while IFS= read -r line do ((count++)) echo $count # # 用"<(...)"这种子shell语法 # done < <(ls -1aF /tmp) printf "Found %u entries\n" "${count}" -------------------------------------------------------------------------- 该方案不用显式创建临时文件。实际上,<(...)子shell语法会在/tmp下隐式创建FIFO。 其他解决方案: -------------------------------------------------------------------------- #!/bin/bash # # 显式创建命名管道 # rm -f /tmp/namedpipe mkfifo /tmp/namedpipe ls -1aF /tmp > /tmp/namedpipe & count=0 while IFS= read -r line do ((count++)) echo $count done < /tmp/namedpipe printf "Found %u entries\n" "${count}" rm -f /tmp/namedpipe -------------------------------------------------------------------------- #!/bin/bash # # {}构成command group # ls -1aF /tmp | { count=0 while IFS= read -r line do ((count++)) echo $count done printf "Found %u entries\n" "${count}" } -------------------------------------------------------------------------- #!/bin/bash # # 可以放在{}外 # count=0 ls -1aF /tmp | { while IFS= read -r line do ((count++)) echo $count done printf "Found %u entries\n" "${count}" } -------------------------------------------------------------------------- #!/bin/bash var=$(ls -1aF /tmp) # # 格式串中务必有\n。避免不以\n结尾的最后一行不被while read处理 # printf "%s\n" "${var}" | { count=0 while IFS= read -r line do ((count++)) echo $count done printf "Found %u entries\n" "${count}" } -------------------------------------------------------------------------- $ ./test.sh 1 2 ... 49 50 Found 50 entries "IFS="用于"while read"时,意在保持每行首尾两端的空白字符,对比如下输出: $ echo " this is a test " | while IFS= read -r line;do echo "[${line}]";done [ this is a test ] $ echo " this is a test " | while read -r line;do echo "[${line}]";done [this is a test] "-r"用于"while read"时,意在取消每行内容中反斜杠\的转义效果,对比如下输出: $ { echo 'this \\ line is \';echo 'continued'; } | while IFS= read -r line;do echo "[${line}]";done [this \\ line is \] [continued] $ { echo 'this \\ line is \';echo 'continued'; } | while IFS= read line;do echo "[${line}]";done [this \ line is continued] 注意,{ ... }内部两端各有一个空格,必须存在。 一般写成"while IFS= read -r line",确保${line}是原始数据。 Q: "while read line"循环如何从变量获取输入,而不是从管道或文件获取输入? A: scz -------------------------------------------------------------------------- #!/bin/bash var=$(ls -1aF /tmp) count=0 while IFS= read -r line do ((count++)) echo $count # # 用<<<从变量获取输入,而不是< # # 搜"bash here string"了解更多细节 # done <<< "${var}" printf "Found %u entries\n" "${count}" -------------------------------------------------------------------------- "bash here string"要求/tmp可写,会隐式创建临时文件。 另有一种类似的"here document": -------------------------------------------------------------------------- #!/bin/bash var=$(ls -1aF /tmp) count=0 while IFS= read -r line do ((count++)) echo $count # # 搜"bash here document"了解更多细节 # # 后面的${var}两侧不要用双引号,不要写注释,否则视为文件内容 # done << EOF ${var} EOF printf "Found %u entries\n" "${count}" --------------------------------------------------------------------------