Bash read 命令

警告
本文最后更新于 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命令读取文件

参数

  1. -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 秒,如果用户还没有输入,就会超时。

  1. -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命令,当然也可以在非交互式环境下,比如脚本中使用。

  1. -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
  1. -n 参数

-n参数指定只读取若干个字符作为变量值,而不是整行读取。

1
2
3
4
$ read -n 3 letter
abcdefghij
$ echo $letter
abc

上面例子中,变量letter只包含 3 个字母。

我们也可以在读取文件的时候,通过指定-n n参数,只读取每一行的前 n 个字符。这里就不演示了,很简单。

  1. -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参数就可以允许用户使用自动补全。

  1. -s 参数

-s参数使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。

简单实践如下:

1
2
3
4
$ read -p "please input your password: " -s passkey
please input your password: 
$ echo $passkey
aaabbb
  1. 其他参数
  • -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 脚本》

0%