Bash read 命令

Bash read 命令

我们在《read 指令》中认真学习过 read 命令

用法

read 命令将用户的输入存入一个或多个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。

read 命令的格式如下。

read [-options] [variable...]

上面语法中,options 是参数选项,variable 是用来保存输入数值的一个或多个变量名。如果没有提供变量名,环境变量 REPLY 会包含用户输入的一整行数据,一般不建议这样做。

简单实践:

创建 script_read

#!/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 输出提示信息

赋权后执行

$ ./script_read
please input name value age
xiashuo xyz 12
your input name:xiashuo value:xyz age:12

用户的输入会按照空格拆分成多个部分,然后按照顺序赋值给 read 声明的多个接收变量,如果用户输入拆分出的部分的个数没有接收变量那么多,那排在后面的变量将为空,如果用户输入拆分出的部分的个数比接收变量那么多,那么多出的部分都会赋予最后一个接收变量

$ ./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 命令将多个用空格分割的部分 视为一个部分,依然会被空格分割开来,对符号继续转义也做不到

$ ./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

#!/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

111 222 333 4444
555 666 777
888
999

赋权之后,执行脚本

$ ./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

#!/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

赋权后执行

$ ./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 命令等待用户输入的时间(单位为秒)。

$ TMOUT=3
$ read response

上面例子也是等待 3 秒,如果用户还没有输入,就会超时。

  1. -p 参数

-p 参数指定用户输入的提示信息。

$ 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 下实践:

$ 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

#!/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

111 222 333 4444
555 666 777
888
999

赋权之后,执行脚本

$ ./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 参数指定只读取若干个字符作为变量值,而不是整行读取。

$ read -n 3 letter
abcdefghij
$ echo $letter
abc

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

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

  1. -e 参数

-e 参数允许用户输入的时候,使用 readline 库提供的快捷键,比如自动补全。具体的快捷键可以参阅《行操作》一章。

简单实践如下

创建脚本 script_read_tab.sh

#!/usr/bin/env bash

read -e -p "please input file: " file
if [ ! \( -f $file  \) ];then
   echo not file path
else
   echo $file
fi

赋权后执行:

$ ./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 参数使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。

简单实践如下:

$ read -p "please input your password: " -s passkey
please input your password: 
$ echo $passkey
aaabbb
  1. 其他参数

IFS 变量

read 命令读取的值,默认是以空格分隔。可以通过自定义环境变量 IFS(内部字段分隔符,Internal Field Separator 的缩写),修改分隔标志。

IFS 变量影响广泛,比如《Bash 脚本》小节中提到的脚本中的 $* 变量,因此不应该随意更改

IFS 的默认值是空格、Tab 符号、换行符号,通常取第一个(即空格)。直接通过 echo $IFS 查看 IFS 的值啥都看不到。

如果把 IFS 定义成冒号(:)或分号(;),就可以分隔以这两个符号分隔的值,这对读取文件很有用。

创建文件 script_read_passwd.sh

#!/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

赋权后执行

$ ./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 会自动恢复原来的值。如果不写在一行,就要采用下面的写法。

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 脚本》