bash中的read命令是读文件和读用户输入时常能够用的指令,但它有一个坑,如果在写bash中不注意的话,很容易出问题。
简化下问题:
例如,如下这个bash:
while read line
do
echo $line
read -p "do you understand ?(y/n)" input
case $input in
[yY][eE][sS]|[yY])
echo "YES"
;;
*)
echo "NO"
;;
esac
done < test.txt
很简单,读test.txt,问下用户,读取输入
test.txt假设是如下几行:
question 1
question 2
question 3
但是执行结果并不尽如人意:
[root@localhost home]# sh test.sh
question 1
NO
question 3
NO
因此既看不到提示输入,又得不到正确的结果……
至于怎么解决……我还没找到……先记录一下问题……
//===========================================================================
牛人解析
1. bash中的while read LINE并不是一次读入整个文件, 而是open文件拿到fd之后, 在while的每一次中, read都读满缓冲区, 然后lseek记录读到的底一个\n偏移量位置, 然后write这一段内容, 进行下一次while
2. 第一次read是读了1\n2\n3\n4\n5\n, 然后指针移动从第0位移动到-8位, 然后write这一段的内容1\n
3. 正常情况下, 第二次读, 会读2\n3\n4\n5\n, 然后指针从0移动到-6位, 然后write这一段内容2\n, 以此类推
4. 在while read中还内嵌了read的时候, 第二次的读, 就变成了while中取用户输入的read, 也即是, 把test.txt第一个\n后第二个\n前的内容读成了用户输入. 然后继续while的过程.
也即是,
while read line
do
echo $line
done < FILE
这段代码实际运行中的伪代码是:
while NOT_EOF_OF_FILE
do
read "$line直到\n, 放到输出缓冲区"
write "输出缓冲区的内容"
done
因此, 在循环中加入read用户输入的代码,
while read line
do
echo $line ## @1
read -p "user input: " input ## @2
echo $input ## @3
done < FILE
这段代码实际运行中的伪代码是:
while NOT_EOF_OF_FILE
do
## @1
read "$line直到\n, 放到输出缓冲区"
write "输出缓冲区的内容"
## @2
read "$line直到\n, 并当作用户输入, 放到输出缓冲区"
## @3
write "输出缓冲区的内容"
done
所以, 假设文本test的内容是:
1
2
3
4
5
则正常输出:
1
2
3
4
5
当while中又重新read时的输出:
1 --> 这一行来自于test文件的第一个\n在第一个while中被read的结果
2 --> 这一行来自于test文件的第二个\n在第一个while中被当作用户输入被读取了
3 --> 这一行来自于test文件的第三个\n在第二个while中被read的结果
4 --> 这一行来自于test文件的第四个\n在第二个while中被当作用户输入被读取了
5 --> 这一行来自于test文件的第五个\n在第三个while中被read的结果
也就是每次while过程中, 都两次read了test的内容. 也即是, 本来5行内容, 被3次while读完了.
因为读取用户输入必须用到read, 不能替换, 所以只能把读取文件的read替换成别的方法. 因此, 可以这样规避这个问题:
for lines in $(/bin/awk '{print $0}' test)
do
echo $lines
read -p "Please input your name: " name
echo $name
done