IFS 变量
IFS,即Internal Field Seprator
,默认为: 空格、制表、换行
多个连续空格会被当成一个空格处理,但是其它两个不会被合并
IFS 与 foreach
不要试图改变 IFS 变量的值来改变 foreach 的元素分割符,因为不生效
比如,我想遍历 IPv4 地址的每个部分(点号作为分隔符),这是最初的写法
1
2
$ IFS='.'; for byte in 192.168.255.120; do echo "$byte"; done
192.168.255.120
那么如何分割 IPv4 地址呢?目前最简单有效的方式就是利用 read 内置命令:
IFS 与 read
1
2
3
4
5
6
$ IFS='.' read -ra bytes <<<"192.168.255.120"
$ for byte in "${bytes[@]}"; do echo "$byte"; done
192
168
255
120
- read 在给 bytes 数组赋值前会先清空原有的元素,不用担心数据污染
-r
表示忽略转义序列,-a
表示将分割后的字符串存入指定的数组- read 命令是从 stdin 中读取数据的,所以需要使用
<<<string
重定向
注意 IFS='.'
与 read
之间没有分号,这并不是拼写错误,而是一个实用的 shell 技巧:
常用来传递临时环境变量(允许多个赋值语句,空格隔开),而不会影响当前环境变量。
read 会去除行首和行尾的空白符,若需要保留,请将IFS
置空,如IFS= read line <foo.txt
。
read 命令格式
在 bash 中执行 help read
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
Reads a single line from the standard input, or from file descriptor FD
if the -u option is supplied. The line is split into fields as with word
splitting, and the first word is assigned to the first NAME, the second
word to the second NAME, and so on, with any leftover words assigned to
the last NAME. Only the characters found in $IFS are recognized as word
delimiters.
If no NAMEs are supplied, the line read is stored in the REPLY variable.
Options:
-a array assign the words read to sequential indices of the array
variable ARRAY, starting at zero
-d delim continue until the first character of DELIM is read, rather
than newline
-e use Readline to obtain the line
-i text use TEXT as the initial text for Readline
-n nchars return after reading NCHARS characters rather than waiting
for a newline, but honor a delimiter if fewer than
NCHARS characters are read before the delimiter
-N nchars return only after reading exactly NCHARS characters, unless
EOF is encountered or read times out, ignoring any
delimiter
-p prompt output the string PROMPT without a trailing newline before
attempting to read
-r do not allow backslashes to escape any characters
-s do not echo input coming from a terminal
-t timeout time out and return failure if a complete line of
input is not read within TIMEOUT seconds. The value of the
TMOUT variable is the default timeout. TIMEOUT may be a
fractional number. If TIMEOUT is 0, read returns
immediately, without trying to read any data, returning
success only if input is available on the specified
file descriptor. The exit status is greater than 128
if the timeout is exceeded
-u fd read from file descriptor FD instead of the standard input
Exit Status:
The return code is zero, unless end-of-file is encountered, read times out
(in which case it's greater than 128), a variable assignment error occurs,
or an invalid file descriptor is supplied as the argument to -u.
read 常用选项
-r
:不要对输入行中的转义序列进行转义(反斜线转义字符序列)-a array
:将读取到的数据行按照 IFS 值进行分割,然后存入数组-p prompt
:在读取前先打印指定的提示字符串(不自动添加换行符)-s
:不要显示用户输入的数据(在录入密码等隐私数据时很有用)-t timeout
:指定读取的超时时间(单位为秒,timeout 可以为小数)-u fd
:从指定的文件描述符中读取,而不是从标准输入流中读取
除了使用数组接收 分割后的单词,还可直接用变量来接收:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ # 按顺序赋给 a,b,c,d 变量
$ IFS='.' read a b c d <<<192.168.100.200
$ echo "a = $a | b = $b | c = $c | d = $d"
a = 192 | b = 168 | c = 100 | d = 200
$ # 变量数多于 word 数时
$ a= b= c= d= # 清空值
$ IFS='.' read a b c d x <<<192.168.100.200
$ echo "a = $a | b = $b | c = $c | d = $d | x = $x"
a = 192 | b = 168 | c = 100 | d = 200 | x =
$ # 变量数少于 word 数时
$ a= b= c= # 清空值
$ IFS='.' read a b c <<<192.168.100.200
$ echo "a = $a | b = $b | c = $c"
a = 192 | b = 168 | c = 100.200
$ # 只有一个变量时,不分割
$ IFS='.' read ip <<<192.168.100.200
$ echo $ip
192.168.100.200
$ # 默认 IFS (空格、制表、换行)
$ read ip <<<192.168.100.200
$ echo $ip
192.168.100.200
$ # 不提供变量时,默认存到 REPLY
$ read <<<192.168.100.200
$ echo $REPLY
192.168.100.200
read 返回值
正常情况下返回 0;当发生以下情况时,read 会返回非 0 值:
- 读到 EOF
- 发生读取超时
- 变量赋值错误
- 无效的文件描述符
第一条最常用,也就是读取到 EOF 时,read 会立即返回 1,可用来退出循环:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat <<<$'www.zfl9.com\nwww.baidu.com\nwww.google.com'
www.zfl9.com
www.baidu.com
www.google.com
$ # read一次只会读取一行
$ read -r line <<<$'www.zfl9.com\nwww.baidu.com\nwww.google.com'
$ echo "$line"
www.zfl9.com
$ # 利用EOF时的返回值来结束循环,读取多行
$ while read -r line; do echo "$line"; done <<<$'www.zfl9.com\nwww.baidu.com\nwww.google.com'
www.zfl9.com
www.baidu.com
www.google.com
可以利用 read 来遍历文件的每一行,不要使用 foreach,会被空白符干扰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ cat data
www zfl9 com
www baidu com
www google com
$ for line in "$(cat data)"; do echo "$((++i)) $line"; done
1 www zfl9 com
www baidu com
www google com
$ i=0
$ for line in $(cat data); do echo "$((++i)) $line"; done
1 www
2 zfl9
3 com
4 www
5 baidu
6 com
7 www
8 google
9 com
$ i=0
$ while read -r line; do echo "$((++i)) $line"; done <data
1 www zfl9 com
2 www baidu com
3 www google com