警告
本文最后更新于 2023-10-30,文中内容可能已过时。
Bash read 命令
我们在《read 指令》中认真学习过 read 命令
用法
read
命令将用户的输入存入一个或多个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。
read
命令的格式如下。
1
|
read [-options] [variable...]
|
上面语法中,options
是参数选项,variable
是用来保存输入数值的一个或多个变量名。如果没有提供变量名,环境变量REPLY
会包含用户输入的一整行数据,一般不建议这样做。
简单实践:
创建script_read
1
2
3
4
|
#!/usr/bin/env bash
echo please input name value age
read name value age
echo your input name:$name value:$value age:$age
|
其实我们可以指定read -p
来输出提示文字,这样就不需要每次执行read
前,通过echo
输出提示信息
赋权后执行
1
2
3
4
|
$ ./script_read
please input name value age
xiashuo xyz 12
your input name:xiashuo value:xyz age:12
|
用户的输入会按照空格拆分成多个部分,然后按照顺序赋值给read
声明的多个接收变量,如果用户输入拆分出的部分的个数没有接收变量那么多,那排在后面的变量将为空,如果用户输入拆分出的部分的个数比接收变量那么多,那么多出的部分都会赋予最后一个接收变量
1
2
3
4
5
6
7
8
|
$ ./script_read
please input name value age
xiasuo xyz 12 89
your input name:xiasuo value:xyz age:12 89
$ ./script_read
please input name value age
11 22
your input name:11 value:22 age:
|
(单/双)引号不能让 read 命令将多个用空格分割的部分 视为一个部分,依然会被空格分割开来,对符号继续转义也做不到
1
2
3
4
5
6
7
8
9
10
11
12
|
$ ./script_read
please input name value age
'111 222 333' sd
your input name:'111 value:222 age:333' sd
$ ./script_read
please input name value age
"111 4444 222" 6666
your input name:"111 value:4444 age:222" 6666
$ ./script_read
please input name value age
\"222 4444 555\" 4444
your input name:"222 value:4444 age:555" 4444
|
read
命令除了读取键盘输入,可以用来读取文件,使用起来也非常方便。
简单实践如下
创建读取文件的脚本script_read_file.sh
1
2
3
4
5
6
7
8
9
10
11
12
|
#!/usr/bin/env bash
if [ $# -lt 1 ];then
echo need at least 1 params
exit 1
fi
if [ ! \( -f $1 \) ];then
echo not file
exit 1
fi
while read line; do
echo $line
done < $1
|
info.txt
1
2
3
4
|
111 222 333 4444
555 666 777
888
999
|
赋权之后,执行脚本
1
2
3
4
5
6
7
8
9
|
$ ./script_read_file.sh
need at least 1 params
$ ./script_read_file.sh aaa
not file
$ ./script_read_file.sh info.txt
111 222 333 4444
555 666 777
888
999
|
上面的例子通过read
命令,读取一个文件的内容。done
命令后面的定向符<
,将文件内容导向read
命令,每次读取一行,存入变量myline
,直到文件读取完毕。
通过 mapfile 命令,可以很简单的实现将文件的每一行读取到数组中的效果,比如你要读取info.txt
,运行mapfile arr < info.txt
即可。非常简单,
参考博客:bash 内置命令 mapfile:读取文件内容到数组 - 骏马金龙 - 博客园
后期我们可以通过添加read
命令的各种参数,自定义读取动作,比如通过read -a
将文件的每一行读取成一个数组,具体请看下面的参数
小节
后期我们在学习《Bash 循环》的for...in 循环
小节的时候,会看通过cat
命令读取文件
参数
-t
参数
read
命令的-t
参数,设置了超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。
简单实践如下
创建script_read_timeout.sh
1
2
3
4
5
6
7
8
|
#!/usr/bin/env bash
echo please input some text in 3 seconds
if read -t 3 txt;then
echo your input is $txt
else
echo not input in 3 secondss
fi
|
赋权后执行
1
2
3
4
5
6
7
|
$ ./script_read_timeout.sh
please input some text in 3 seconds
not input in 3 seconds
$ ./script_read_timeout.sh
please input some text in 3 seconds
2
your input is 2
|
上面例子中,输入命令会等待 3 秒,如果用户超过这个时间没有输入,这个命令就会执行失败($?
不为 0)。if
根据命令的返回值,转入else
代码块,继续往下执行。
环境变量TMOUT
也可以起到同样作用,指定read
命令等待用户输入的时间(单位为秒)。
1
2
|
$ TMOUT=3
$ read response
|
上面例子也是等待 3 秒,如果用户还没有输入,就会超时。
-p
参数
-p
参数指定用户输入的提示信息。
1
2
3
4
|
$ read -p "please input your name:" name
please input your name:xiashuo
$ echo $name
xiashuo
|
上面例子中,先显示please input your name:
,再接受用户的输入。
上面的例子是在交互式的 shell 中使用read
命令,当然也可以在非交互式环境下,比如脚本中使用。
-a
参数
-a
参数把用户的输入赋值给一个数组,从零号位置开始。
在交互式 shell 下实践:
1
2
3
4
5
6
|
$ read -p "please input some names:" -a names
please input some names:Jone Alice Mike Jack
$ echo ${names[@]}
Jone Alice Mike Jack
$ echo ${#names[@]}
4
|
上面例子中,用户输入被赋值给一个数组names
。
我们也可以在读取文件的时候,通过-a
参数,将读取到的每一行,根据空格,转化成一个数组来处理,这在处理配按空格分割的置文件的时候,相当有用。
如果你不想根据空格来拆分元素,而是向通过别的符号比如:
、;
等等,那么你可以先自定义IFS
,然后再开始读取读取文件,具体请看IFS 变量
小节
创建读取文件的脚本script_read_file.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#!/usr/bin/env bash
if [ $# -lt 1 ];then
echo need at least 1 params
exit 1
fi
if [ ! \( -f $1 \) ];then
echo not file
exit 1
fi
while read -a line; do
echo Array Length: ${#line[@]}
echo Array: ${line[@]}
done < $1
|
info.txt
1
2
3
4
|
111 222 333 4444
555 666 777
888
999
|
赋权之后,执行脚本
1
2
3
4
5
6
7
8
9
|
$ ./script_read_file.sh info.txt
Array Length: 4
Array: 111 222 333 4444
Array Length: 3
Array: 555 666 777
Array Length: 1
Array: 888
Array Length: 1
Array: 999
|
-n
参数
-n
参数指定只读取若干个字符作为变量值,而不是整行读取。
1
2
3
4
|
$ read -n 3 letter
abcdefghij
$ echo $letter
abc
|
上面例子中,变量letter
只包含 3 个字母。
我们也可以在读取文件的时候,通过指定-n n
参数,只读取每一行的前 n 个字符。这里就不演示了,很简单。
-e
参数
-e
参数允许用户输入的时候,使用readline
库提供的快捷键,比如自动补全。具体的快捷键可以参阅《行操作》一章。
简单实践如下
创建脚本script_read_tab.sh
1
2
3
4
5
6
7
8
|
#!/usr/bin/env bash
read -e -p "please input file: " file
if [ ! \( -f $file \) ];then
echo not file path
else
echo $file
fi
|
赋权后执行:
1
2
3
4
5
6
|
$ ./script_read_tab.sh
please input file: /opt/shell_script/info.txt
/opt/shell_script/info.txt
$ ./script_read_tab.sh
please input file: /opt/shell_script/
not file path
|
上面例子中,read
命令接受用户输入的文件名。这时,用户可能想使用 Tab 键的文件名“自动补全”功能,但是read
命令的输入默认不支持readline
库的功能。-e
参数就可以允许用户使用自动补全。
-s
参数
-s
参数使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。
简单实践如下:
1
2
3
4
|
$ read -p "please input your password: " -s passkey
please input your password:
$ echo $passkey
aaabbb
|
- 其他参数
-d delimiter
:定义字符串delimiter
的第一个字符作为用户输入的结束,而不是一个换行符。
-r
:raw 模式,表示不把用户输入的反斜杠字符解释为转义字符。
-u fd
:使用文件描述符fd
作为输入。
IFS 变量
read
命令读取的值,默认是以空格分隔。可以通过自定义环境变量IFS
(内部字段分隔符,Internal Field Separator 的缩写),修改分隔标志。
IFS
变量影响广泛,比如《Bash 脚本》小节中提到的脚本中的$*
变量,因此不应该随意更改
IFS
的默认值是空格、Tab 符号、换行符号,通常取第一个(即空格)。直接通过echo $IFS
查看IFS
的值啥都看不到。
如果把IFS
定义成冒号(:
)或分号(;
),就可以分隔以这两个符号分隔的值,这对读取文件很有用。
创建文件script_read_passwd.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#!/usr/bin/env bash
# read-ifs: read fields from a file
FILE=/etc/passwd
read -p "Enter a username > " user_name
file_info="$(grep "^$user_name:" $FILE)"
if [ -n "$file_info" ]; then
IFS=":" read user pw uid gid name home shell <<< "$file_info"
echo "User = '$user'"
echo "UID = '$uid'"
echo "GID = '$gid'"
echo "Full Name = '$name'"
echo "Home Dir. = '$home'"
echo "Shell = '$shell'"
else
echo "No such user '$user_name'" >&2
exit 1
fi
|
赋权后执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
$ ./script_read_passwd.sh
Enter a username > root
User = 'root'
UID = '0'
GID = '0'
Full Name = 'root'
Home Dir. = '/root'
Shell = '/bin/bash'
$ ./script_read_passwd.sh
Enter a username > admin
User = 'admin'
UID = '1000'
GID = '1000'
Full Name = ''
Home Dir. = '/home/admin'
Shell = '/bin/bash'
$ ./script_read_passwd.sh
Enter a username > xx
No such user 'xx'
|
上面例子中,IFS
设为冒号,然后用来分解/etc/passwd
文件的一行。IFS
的赋值命令和read
命令写在一行,这样的话,IFS
的改变仅对后面的命令生效,该命令执行后IFS
会自动恢复原来的值。如果不写在一行,就要采用下面的写法。
1
2
3
4
|
OLD_IFS="$IFS"
IFS=":"
read user pw uid gid name home shell <<< "$file_info"
IFS="$OLD_IFS"
|
另外,上面例子中,<<<
是 Here 字符串,用于将变量值转为标准输入,因为read
命令只能解析标准输入。
关于 Here 文档,请看《转义和引号》的Here 字符串
小节
总结
通过read
命令可以获取参数,这种的做法一般用于交互式的脚本,但是这样的话,脚本就无法以非交互式的方式一键运行了,因此如果可以的话,尽量不要使用read
来传递参数,而是通过getopt
命令来获取参数
关于getopt
命令,请看《Bash 脚本》