示例shell脚本
#!/bin/bash
# read-ifs: change ifs (internal fields separator) then read input from a file
FILE=/etc/passwd
read -p "Please enter a user name > " user_name
user_record=$(grep "^$user_name:" $FILE)
if [[ -n "$user_record" ]]; then
OLD_IFS=$IFS
IFS=":"
# 如果 "$user_record" 没有双引号,read 读取的值会全部赋值给第一个变量 user
read user pw uid gid fname home shell <<< "$user_record"
cat <<- _EOF_
User = $user
UID = $uid
GID = $gid
Full name = $fname
Home dir. = $home
Shell = $shell
_EOF_
IFS=$OLD_IFS
else
echo "No such user: $user_name" >&2
exit 1
fi
exit
总是使用双引号包围「变量替换」和「命令替换」
如果不使用双引号,当输入或参数包含空白或特殊字符\[*?
时,脚本执行会失败。
这个答案适用于Bourne/POSIX风格的shell(sh
、ash
、dash
、bash
、ksh
、mksh
、yash
……)。Zsh用户应该跳过它并阅读何时必须使用双引号?代替。如果你想了解所有情况,请阅读标准或您的shell的手册。
为什么我需要写"$foo"
?没有引号会怎么样?
$foo
并不仅仅表示「取变量foo
的值」。它的意思要复杂得多:
- 首先,取出变量的值。
- 字段拆分:将该值视为空格分隔的字段列表,并生成结果列表。例如,如果变量包含
foo
、*
、bar
,则此步骤的结果是3元素列表foo
、*
、bar
。 - 文件名生成:将每个字段视为glob,即通配符模式,并将其替换为与该模式匹配的文件名称列表。如果模式与任何文件不匹配,则不会被修改。在我们的例子里,列表包含
foo
,接下来是当前目录的所有文件的列表,最后是bar
。如果当前目录没有文件,列表就是foo
、*
、bar
。
请注意,结果是字符串列表。shell语法有两个上下文:列表上下文和字符串上下文。字段拆分和文件名生成仅在列表上下文中发生,但这是大部分时间。双引号分隔字符串上下文:整个双引号字符串是单个字符串,不分割。(例外:"$@"
扩展到位置参数列表,例如"$@"
相当于"$1" "$2" "$3"
,如果有三个位置参数的话。请参阅$*
和$@
的区别。)
用$(foo)
或`foo`
命令替换也是一样。注意,不要使用`foo`
:它的引用规则是奇怪的,不可移植的,所有现代shell都支持$(foo)
,除了具有直观的引用规则之外,(在各种现代shell)绝对是等价的。
算术替换的输出也经历相同的扩展,但这通常不是一个问题,因为它只包含不可扩展的字符(假设IFS
不包含数字或-
)。
你只要记住 总是 使用双引号围绕变量和命令替换,除非你了解所有这些将要发生的繁琐情况。千万要小心:不使用引号不仅会导致错误,还会导致安全漏洞。
其他与shell引号相关的问题
- 浏览StackExchange网站上的/quoting标签,或者是/shell或/shell-script标签。(点击「learn more…」,查看一些常见提示和手工选择的常见问题列表。)
- 《Unix & Linux大学教程》第13章13.3节:强引用和弱引用
备注
- 这里的「substitutions」也被翻译为「展开」,个人更喜欢「替换」,更加直观。
- 在计算机编程中,特别是在类Unix环境中,glob模式指的是「具有通配符的文件名集合」。例如,Unix命令
mv *.txt textfiles/
移动(mv
)当前目录所有以.txt
结尾的文件到textfiles
目录。这里,*
是一个通配符,代表「任何字符串」,*.txt
是一个glob模式。详见:https://en.wikipedia.org/wiki/Glob_(programming)
Leave A Comment