高级shell编程笔记(第三十一章 Gotchas)

第三十一章 Gotchas

将保留字和字符声明为变量名

case=value0   #引发错误
23skidoo=value1   #引发错误
#以数字开头的变量名是由shell保留使用的
#试试_23skidoo=value1. 用下划线开头的变量名是允许的

#但是... 仅使用下划线来用做变量名也是不行的
_=25
echo $_   #$_是一个特殊的变量,被设置为最后的命令的最后一个参数

xyz((!*=value2   #引起严重错误

用连字符或其他保留字符当做变量(或函数名)

var-1=23   #用"var_1"代替

function-whatever()   #用 'function_whatever ()' 代替

function.whatever () # 错误 用 'functionWhatever ()' 代替.

给变量和函数使用相同的名字。这会使脚本不能分辨两者

do_something()
{
    echo "This function does something with \"$1\"."
}

do_something=do_something

do_something do_something
#这些都是合法的,但让人混淆

不适当地使用宽白字符。和其它的编程语言相比,Bash非常讲究空白字符的使用

var1 = 23   #'var1=23' 是正确的

let c = $a -$b   # 'let c=$a-$b' 或 'let "c = $a - $b"'是正确的.

if [ $a -le 5]   # if [ $a -le 5 ] 是正确的.
# if [ "$a" -le 5 ] 会更好
# [[ $a -le 5 ]] 也可以

未初始化的变量被认为是NULL值,而不是有零值。

#!/bin/bash
echo "uninitialized_var = $uninitialized_var"
#uninitialized_var =

混淆测试里的 = 和 -eq 操作符。请记住:= 是比较字符变量,而 -eq 比较整数。

if [ "$a" = 273 ]   #$a 是一个整数还是一个字符串?
if [ "$a" -eq 273 ] # 如果$a 是一个整数,用这个表达式.

#有时你能混用 -eq 和 = 而没有不利的结果.
#然而 . . .

a=273.0   #不是一个整数

if [ "$a" = 273 ];then
  echo "比较工程."
else
  echo "比较不起作用."
fi

# 与 a=" 273" 和 a="0273" 一样.
# 同样, 问题仍然是试图对非整数值使用 "-eq" 测试.
if [ "$a" -eq 273.0 ];then
 echo "a = $a"
fi   #因错误信息而中断.
# test.sh: [: 273.0: integer expression expected

误用字符串比较操作符
Example 31-1 数字和字符串比较是不相等同的

#!/bin/bash
#
echo
number=1

while [ "$number" < 5 ]   # 错误! 应该是: while [ "$number" -lt 5 ]
do
  echo -n "$number"
  let "number += 1"
done

echo "----------------------"
while [ "$number" \< 5 ]
do
  echo -n "$number"   # 看起来好像是能工作的, 但它其实是在对 ASCII 码的比较,而非是对数值的比较.
  let "number += 1"
done

echo;echo "---------------------"

lesser=5
greater=105

if [ "$greater" \< "$lesser" ];then
  echo "$greater is less than $lesser"   #事实上, "105" 小于 "5". 是因为使用了字符串比较 (以 ASCII 码的排序顺序比较).
fi
echo
exit 0  

有时在测试时的方括号([])里的变量需要引用起来(双引号)。如果用户没有这么做可能会引起不可预料的结果。参考例子7-6,例子16-5和例子9-6

在脚本里的命令可能会因为脚本没有运行权限而导致运行失败。如果用户不能在命令行里调一个命令,即使把这个命令加到一个脚本中也一样会失败。这时可以尝试更改访问命令的属性,甚至可能给他设置suid位(当然是以root来设置)。

试图用 - 来做重定向操作(事实上它不是操作符)会导致令人讨厌的意外。

command1 2> - | command2   #试图把 command1 的错误重定向到一个管道里.
#不会工作

command1 2>& - |command2   #也没效果

用Bash版本2+的功能可以当有错误信息时引发修复动作。

#!/bin/bash
#
minimun_version=2
E_BAD_VERSION=80

if [ "$BASH_VERSION" \< "$minimun_version" ];then
  echo "此脚本仅适用于Bash版本$minimum或更高版本"
  echo "强烈建议升级"
  exit $E_BAD_VERSION
fi
...  

在非Linux的机器上使用Bourne shell脚本(#!/bin/sh)的Bash专有功能可能会引起不可预料的行为。Linux系统通常都把sh取别名为bash,但在其他的常见的UNIX系统却不一定是这样。

一个带有DOS分格新行符(\r\n)的脚本会执行失败,因为#!/bin/bash\r\n不是合法的,不同与合法的#!/bin/bash\n。解决办法就是把脚本转换成UNIX分格的新行符。

#!/bin/bash
#
echo "Here"
unix2dos $0   #脚本先把自己改成DOS格式
chmod 755 $0   #更改回执行权限.  'unix2dos'命令会删除执行权限.

./$0
echo "There"   #脚本尝试再次运行自己本身.但它是一个 DOS 文件而不会正常工作了.
exit

shell脚本以#!/bin/sh开头将不会在Bash兼容的模式下运行。一些Bash专有的功能可能会被禁用掉。那些需要完全使用Bash专有扩展特性的脚本应该使用#!/bin/bash开头。

脚本里在here document的终结输入的字符串前加入空白字符会引起不可预料的结果。

脚本不能export(导出)变量到它的父进程,或父进程的环境里。就像我们学的生物一样,一个子进程可以从父进程里继承但不能去影响父进程。

WHATEVER=/home/bozo
export WHATEVER
exit 0


bash$ echo $WHATEVER
bash$

可以确定,回到命令提示符,$WHATEVER变量仍然没有设置。

在子shell设置和操作变量,然后尝试在子shell的作用范围外使用相同的变量将会导致非期望的结果。

Example 31-2 子shell缺陷

#!/bin/bash
#
outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo
(
#子shell开始
echo "outer_variable inside subshell = $outer_variable"
inner_variable=inner
echo "inner_variable inside subshell = $inner_variable"
outer_variable=inner
echo "outer_variable inside subshell = $outer_variable"
#export inner_variable
#export outer_variable
)
echo
echo "inner_variable outside subshell = $inner_variable"
echo "outer_variable outside subshell = $outer_variable"
echo
exit 0

把echo的输出用管道输送给read命令可能会产生不可预料的结果。在这个情况下,read表现的好像它不是在一个子shell里一样。可用set命令代替。

Example 31-3 把echo的输出用管道输送给read命令

#!/bin/bash
#
a=aaa
b=bbb
c=ccc

echo "one two three" | read a b c   #试图重新给 a, b, 和 c 赋值.
echo
echo "a = $a"    #aaa
echo "b = $b"    #bbb
echo "c = $c"    #ccc
#重新赋值失败.

#用下面的另一种方法.
var=`echo "one two three"`
set -- $var
a=$1;b=$2;c=$3
echo "---------"
echo "a = $a"   # a = one
echo "b = $b"   # b = two
echo "c = $c"   # c = three
#重新赋值成功.
#-----------------

#请注意 echo 值到'read'命令里是在一个子 SHELL 里起作用的.所以,变量的值只在子 SHELL 里被改变了.
a=aaa
b=bbb
c=ccc

echo;echo
echo "one two three" | (
    read a b c;
    echo "Inside subshell:";
    echo "a = $a";   #one
    echo "b = $b";   #two
    echo "c = $c"   #three
)
echo "---------------"
echo "Outside subshell: "
echo "a = $a"    #aaa
echo "b = $b"    #bbb
echo "c = $c"    #ccc

exit 0
#循环管道问题

foundone=false
find $HOME -type f -atime +30 -size 100k |
while true
do
  read f
  echo "$f is over 100KB and has not been accessed in over 30 days"
  echo "Consider moving the file to archives."
  foundone=true
  echo "Subshell level = $BASH_SUBSHELL"   #Subshell level = 1. 现在是在子 shell 里头运行.
done

#foundone 变量在此总是有 false 值,因此它是在子 SHELL 里被设为 true 值的
if [ $foundone = fales ];then
  echo "No files need archiving."
fi

#  =================现在, 使用正确的方法:===========
foundone=false
for f in $(find $HOME -type f -atime +30 -size 100k)   #没有使用管道.
do
  echo "$f is over 100KB and has not been accessed in over 30 days"
  echo "Consider moving the file to archives."
  foundone=true
done

if [ $foundone = fales ];then
  echo "No files need archiving."
fi 

#  =================另一种方法:===========
#脚本中读变量值的相应部分替换在代码块里头读变量,这使变量能在相同的子 SHELL 里共享了.
find $HOME -type f -atime +30 -size 100k | {
    foundone=false
    while read f
    do
      echo "$f is over 100KB and has not been accessed in over 30 days"
      echo "Consider moving the file to archives."
      foundone=true
    done
    
    if ! $foundone
    then
      echo "No files need archiving."
    fi  
}

相关的问题是:当尝试写 tail -f 的输出给管道并传递给 grep 时会发生问题.

tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log
# "error.log"文件里将不会写入任何东西.

在脚本中使用"suid" 的命令是危险的,因为这会危及系统安全。

用 shell 编写 CGI 程序是值得商榷的。Shell 脚本的变量不是"类型安全的",这样它用于 CGI 连接使用时会引发不希望的结果。其次,它很难防范黑客的攻击。

Bash 不能正确处理双斜线 (//) 字符串。

Linux 或 BSD 上写的 Bash 脚本可能需要修正以使它们也能在商业的 UNIX (或 Apple OSX)上运行。这些脚本常使用比一般的 UNIX 系统上的同类工具更强大功能的 GNU 命令和过滤工具。这方面一个明显的例子是文本处理工具 tr。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值