shell避坑指南


约定

  • Error:错误写法;
  • Bad:是正确写法,但不是推荐写法;
  • Good:不仅是正确写法,而且是推荐的写法。

空格避坑

避坑:带有空格的字符串在进行判断操作时要加引号

示例:

str="this is a example"
[ -n ${str} ]       # Error: 单中括号不能正确处理有空格的情况
[ -n "${str}" ]     # Good: 加引号后可以正确处理
[[ -n ${str} ]]     # Bad: 双中括号可以正确处理
[[ -n "${str}" ]]   # Good: 双中括号虽然可以正确处理,但建议还是加引号

避坑:带有空格的字符串在进行路径操作时要加引号

示例:

mkdir -p test 1/test 2/test3        # Error
mkdir -p "test 1"/"test 2"/test3    # Good:建议分开加引号
mkdir -p "test 1/test 2/test3"      # Bad

cd "test 1"/"test 2"/test3          # Good:建议分开加引号
cd "test 1/test 2/test3"            # Bad

rm -rf "test 1"                     # Good
rm -rf "test 1"/"test 2"            # Good:建议分开加引号
rm -rf "test 1/test 2"              # Bad

避坑:带有空格的字符串在进行”/*”路径操作时要加引号

示例:

# 移动
mv "test 1/*" test2    # Error:带有空格的路径,进行/*操作时这样写错误
mv "test 1"/* test2    # Good

# 复制
cp "test 1/*" test2    # Error:同理
cp "test 1"/* test2    # Good

# 删除
rm -rf "test 1/*"      # Error:无法删除`test 1`目录下的内容
rm -rf "test 1"/*      # Good

避坑:含有通配符的*的字符串在操作时不可加引号,否则无法正常工作

rm -rf /home/zdy/Ascend-mindxedge*.tar.gz      # Good
rm -rf /home/zdy/"Ascend-mindxedge*.tar.gz"    # Error:无法删除

rm -rf /home/zdy/Ascend-mindxedge*             # Good
rm -rf /home/zdy/"Ascend-mindxedge*"           # Error:无法删除

删除文件避坑

避坑:rm -rf ${path}/*时要确保${path}存在,否则变砖

说明:
如果${path}为空,则rm -rf ${path}/*变为rm -rf /*,会把根目录下的所有文件删完,变砖。

避坑:rm -rf删除目录软连接时不要加末尾斜杠

示例:

# 假定test是指向目录的软连接
rm -rf test     # Bad:可以删除test软连接,而不影响真实目录
rm -rf test/    # Error:不会删除test软连接,而是删除test指向目录下的所有文件
rm -f test      # Bad:可以删除test软连接,而不影响真实目录
rm -f test/     # Error:无法删除,提示:rm: cannot remove 'test/': Is a directory
unlink test     # Good

函数调用避坑

避坑:函数只能返回数字,不能返回字符串

示例:

function fun1() {    # Good
return 1
}

function fun2() {    # Bad
return "1"
}

function fun3() {    # Error
return "T"
}

说明:
shell不区分数字和字符串,所有内容都是字符串。所以fun1和fun2,return都会解释为返回数字;fun3的"T"无法解释为数字,错误。

避坑:函数直接返回其它函数需要用$(fun)

function fun1() {
return 100
}

function fun2() {    # Error:「return: fun1: numeric argument required」
return fun1
}

function fun3() {    # Good:这种写法可以准确的返回fun1的错误码100
return $(fun1)
}

function fun4() {    # Bad
fun1
return $?
}

function fun5() {    # Good
fun1
local ret=$?

# do something ...

return ${ret}
}

function fun6() {    # Good:做一层封装,会丢失fun1的错误码,
if fun1; then            # 如果只想判断fun1是否执行成功而不关心系错误码可用此方法
return 0
else
return 1
fi
}

避坑:在if中判断函数是否执行成功,不要加$(fun)

# case 1
if myfun arg1 arg2; then             # Good:直接调用
echo "function exe success"
# do something
else
echo "fun exe failed"
# do something
fi

# case 2
if $(myfun arg1 arg2); then          # Bad:某些情况下会失败
echo "function exe success"
# do something
else
echo "fun exe failed"
# do something
fi

注意:
shell命令执行成功返回0,失败为非0。因此用if myfun ...这种方式,必须保证myfun在执行成功时返回0。

避坑:同时获取函数执行结果和执行状态时,注意local用法避坑

function test() {
echo "here"
return 1
}

# case 1
function main() {
local value=$(test)        # Error:value能捕获到test的运行结果"here",但$?始终是0,不能捕获到test返回值1
if (($? == 0)); then
echo "right"
else
echo "error"
fi
}

# case 2
function main() {
local value
value=$(test)              # Good:value能捕获到test的运行结果"here",$?也能捕获到test返回值1
if (($? == 0)); then
echo "right"
else
echo "error"
fi
}

# case 3
function main() {
value=$(test)              # Bad:value能捕获到test的运行结果"here",$?也能捕获到test返回值1,但用的是全局变量,不推荐
if (($? == 0)); then
echo "right"
else
echo "error"
fi
}

main

运算符避坑

避坑:”-eq”的含义不完全等于”==”

# case 1
[[ 1 == 1 ]]          # Bad
echo $?    # 0

# case 2
[[ 01 == 1 ]]         # Error
echo $?    # 1

# case 3
[[ 01 -eq 1 ]]        # Good
echo $?    # 0

# case 4
[[ "01" -eq "1" ]]    # Bad
echo $?    # 0

# case 5
((01 == 1))           # Good
echo $?    # 0

# case 6
(("01" == "1"))       # Bad
echo $?    # 0

说明:

  • 在shell中一切变量其实都是字符串,并没有数字这个概念,例如:var=1var="1",本质上是没有区别的。所以为了进行算数运算,一般都要用exprlet等命令;为了进行算数比较,一般要用-eq-ne等比较运算。这些命令和运算符会自动把字符串解释成数字。因此case 3和case 4其实是等价的;
  • ==既可以用来判断字符串是否相等,又可以用来判断数字是否相等。为了不混淆,如果明确要判断数字,最好用-eq而不是==,或者用case 5的方式;如果明确要字符串比较最好用[[ "str1" == "str2" ]]。对于-ne-le等其它算数运算符原则上类同-eq

避坑:字符串空和非空判断

var1=
[[ -z" ${var1}" ]]    # 结果为0
[[ -n "${var1}" ]]    # 结果为1

var2=""
[[ -z "${var2}" ]]    # 结果为0
[[ -n "${var2}" ]]    # 结果为1

var3="ABC"
[[ -z "${var3}" ]]    # 结果为1
[[ -n "${var3}" ]]    # 结果为0

说明:
在shell中var1=var2=""的含义是一样的。因为shell中所有变量都为字符串,而且可加引号也可不加引号。

避坑:变量是否被定义

# var1未被定义
var1=
echo ${var1:+word}
echo ${var1}

# var2未被定义
var2=""
echo ${var2:+word}
echo ${var2}

# var3被定义
var3="ABC"
echo ${var3:+word}
echo ${var3}

说明:
在shell中var1=var2=""的含义是一样的。因为shell中所有变量都为字符串,而且可加引号也可不加引号。

避坑:shell不支持小数运算,若要进行小数运算需要借助bc或awk

软连接避坑

避坑:当ln的目标是目录时,要防止循环指向

# Error
mkdir /root/test_dir
# 第一次执行soft_test指向test_dir
ln -sf /root/test_dir /root/soft_test
# 再次执行会导致test_dir下创建一个这样的软连接:test_dir -> /root/test_dir,导致循环指向
ln -sf /root/test_dir /root/soft_test

# Good
mkdir /root/test_dir
# 第一次执行soft_test指向test_dir
ln -sfn /root/test_dir /root/soft_test
# 再次执行,不会导致循环指向
ln -sfn /root/test_dir /root/soft_test

原因:
加上-n参数,会把test_dir看做一个文件,而不是目录,则不会进入test_dir下创建软连接。如果不加-n,则test_dir是目录,创建软连接时会进入这个目录,并在它下边创建软连接。

推荐写法

字符串比较

# 相等
if [[ "${string1}" == "${string2}" ]]; then
echo "The two strings are the same"
fi

# 不等
if [[ "${string1}" != "${string2}" ]]; then
echo "The two strings are different"
fi

# 为空
if [[ -z "${string}" ]]; then
echo "empty string"
fi

# 非空
if [[ -n "${string}" ]]; then
echo "string is not empty"
fi

# ${substring}在${string}中
if [[ "${string}" == *"${substring}"* ]]; then
echo "${string} contains: ${substring}"
fi

# ${substring}在${string}中
if [[ "${string}" =~ "${substring}" ]]; then
echo "${string} contains: ${substring}"
fi

数值比较

# 相等
if ((int1 == int2)); then
echo "equal"
fi

# 不等
if ((int1 != int2)); then
echo "not equal"
fi

# 大于
if ((int1 > int2)); then
echo "greater"
fi

# 大于等于
if ((int1 >= int2)); then
echo "greater or equal"
fi

# 小于
if ((int1 < int2)); then
echo "lesser"
fi

# 小于等于
if ((int1 <= int2)); then
echo "lesser or equal"
fi

原创文章,作者:kirin,如若转载,请注明出处:https://blog.ytso.com/tech/aiops/275913.html

(0)
上一篇 2022年7月21日 22:40
下一篇 2022年7月21日 22:40

相关推荐

发表回复

登录后才能评论