wiki
  • Introduction
  • Ansible
    • 1 初识Ansible
    • 2 Ansible Inventory 配置详解
    • 3 Ansible Ad-hoc 命令集
    • 3 Ansible lineinfile 模块详解
    • 4 Ansible Playbook
      • 4.1 Playbook的结构及handler用法
      • 4.2 Playbook循环语句
      • 4.3 Playbook条件语句
      • 4.4 Playbook高级用法
      • 4.5 Playbook之tags
      • 4.6 巧用Roles
      • 4.7 文件管理模块及Jinja2过滤器
      • 4.8 yaml 语法
    • 5 Ansible变量
      • 5.1 自定义变量
      • 5.2 Fact变量
      • 5.3 魔法变量
      • 5.4 使用lookup生成变量
      • 5.5 使用vault配置加密
    • 6 Ansible使用优化
  • Linux
    • linux简介
    • 文件管理
      • 查看文件类型
      • 查看文件详细属性
      • 创建、复制、移动、删除
      • 查看文件内容
      • 打包、压缩
    • 文件权限
      • 基本权限UGO
    • 计划任务
    • 用户管理
    • 进程管理
    • 管道及I/O重定向
    • 软件包管理
    • 网络管理
    • 日志logrotate
    • /etc/security/limits.conf 详解与配置
    • 常用知识
  • Shell
    • 第一部分 初见shell
      • 1. 为什么使用shell编程
      • 2. 和Sha-Bang(#!)一起出发
        • 2.1 调用一个脚本
        • 2.2 牛刀小试
    • 第二部分 shell基础
      • 3. 特殊字符
      • 4. 变量与参数
        • 4.1 变量替换
        • 4.2 变量赋值
        • 4.3 Bash弱类型变量
        • 4.4 特殊变量类型
      • 5. 引用
        • 5.1 引用变量
        • 5.2 转义
      • 6. 退出与退出状态
      • 7. 测试
        • 7.1 测试结构
        • 7.2 文件测试操作
        • 7.3 其他比较操作
        • 7.4 嵌套 if/then 条件测试
        • 7.5 牛刀小试
      • 8. 运算符相关话题
        • 8.1 运算符
        • 8.2 数字常量
        • 8.3 双圆括号结构
        • 8.4 运算符优先级
    • 第三部分 shell进阶
      • 9. 换个角度看变量
        • 9.1 内部变量
        • 9.2 变量类型标注:declare 与 typeset
          • 9.2.1 declare 的另类用法
        • 9.3 $RANDOM:生成随机数
      • 10. 变量处理
        • 10.1 字符串处理
          • 10.1.1 使用 awk 处理字符串
          • 10.1.2 参考资料
        • 10.2 参数替换
      • 11. 循环与分支
        • 11.1 循环
        • 11.2 嵌套循环
        • 11.3 循环控制
        • 11.4 测试与分支
      • 12. 命令替换
      • 13. 算术扩展
    • 第四部分 命令
    • 第五部分 高级话题
      • 18.正则表达式
        • 18.1正则表达式简介
        • 18.2文件名替换
        • 18.3 正则表达式对照表
      • 19. 嵌入文档
      • 20. I/O 重定向
        • 20.1 使用 exec
        • 20.2 重定向代码块
        • 20.3 应用程序
      • 21. 子shell
      • 22. 限制模式的Shell
      • 23. 进程替换
      • 24. 函数
        • 24.1 复杂函数和函数复杂性
        • 24.2 局部变量
        • 24.3 不适用局部变量的递归
      • 25. 别名
      • 26. 列表结构
      • 27. 数组
      • 30. 网络编程
      • 33. 选项
      • 34. 陷阱
      • 36. 其他
        • 36.5 有颜色的脚本
        • 36.11 ssh远程操作
    • 第六部分 Google Shell 风格指南
    • 第七部分 实例
  • 3 Python
    • 3.1 简介
    • 3.2 数据类型
    • python subprocess模块的使用
    • python解析nginx配置文件
    • python调用jenkinsAPI批量拷贝视图job
    • python调用zabbixAPI批量添加web监控
    • python监控activemq集群
  • 4 Golang
    • 4.1 程序结构
    • 4.2 数据类型
      • 4.2.1 基础数据类型
      • 4.2.2 复合数据类型
    • 4.3 函数
  • Docker
    • Docker文章推荐
    • Docker Harbor
    • Docker Harbor HA
    • 快速搭建rabbitmq高可用集群
  • Nginx
    • 1.初识Nginx
    • 2.nginx架构基础
    • 3.nginx配置文件详解
      • 3.1 nginx全局块和events块配置
      • 3.2 nginxHTTP块配置
      • 3.3 nginxSERVER块配置
      • 3.4 nginxLOCATION块配置
      • 3.5 nginx常用模块
    • 4.内核参数优化
    • 5.状态码
    • 6.nginx的常见问题
      • Nginx出现403 forbidden的三种原因
  • 8 Tomcat
    • 8.1 tomcat简介
    • 生产环境中的tomcat配置
    • tomcat假死现象
    • Tomcat 错误代号及状态码
    • tomcat注意事项
  • 9 Elasticsearch
    • 9.1 概述
      • 9.1.1 Elasticsearch 简介及其发展历史
      • 9.1.2 Elastic Stack 家族成员及其应用场景
    • 9.2 安装上手
      • 9.2.1 Elasticsearch的安装和简单配置
      • 9.2.2 Kibana的安装和界面快速浏览
      • 9.2.3 在Docker容器中运行Elasticsearch,Kibana和Cerebro
    • 9.3 Elasticsearch入门
      • 9.3.1 基本概念
      • 9.3.2 文档的基本操作CRUP与批量操作
      • 9.3.3 倒排索引入门
      • 9.3.4 通过分析器进行分词
      • 9.3.5 Search API概览
      • 9.3.6 URI Search详解
      • 9.3.7 Request Body和Query DSL简介
  • 10 Zabbix
    • 10.1 zabbix4.4新功能
    • 10.2 zabbix借助ansible安装
    • 10.3 zabbix添加报警媒介
    • 10.4 MySQL监控模块
    • 10.5 Redis监控模块
    • 10.6 常见问题
    • 10.7 用zabbix api批量添加web监控
    • 10.8 activemq监控
    • 10.9 zookeeper集群监控
    • 10.10 Todolist
  • 11 消息中间件
    • 11.1 activemq集群
    • 11.2 rabbitmq
  • 12 常见集群的搭建
    • 12.1 keepalived和heartbeat对比
    • 12.2 zookeeper集群
    • 12.3 fastdfs
    • 12.4 rocketmq集群的搭建
  • 13 常见服务的搭建
    • none
    • 13.2 jdk
    • 13.3 python3.6
    • 13.4 新建博客方法
    • 13.5 confluence
  • 14 Markdown
    • 14.1 让Markdown写作更简单Typora完全使用指南
  • 16 Mysql
    • 16.1 MySQL基本操作
    • 16.2 列表类型和数据完整性
    • 16.3 数据库设计与查询语句
    • 16.4 视图、事务、索引、函数
    • 16.5 MySQL存储引擎
    • 16.6 用户权限和访问控制
    • 16.7 备份恢复
    • 16.8 AB复制
    • 16.9 主主和keepalived
    • 16.10 读写分离和Amoeba
    • 16.11 MySQL逻辑架构
    • 16.12 MySQL锁等待
    • 16.13 MySQL常见问题
    • 16.14 MySQL bin-log日志清理
  • 17 Redis
    • 17.1 安装
    • 17.2 配置
    • 17.3 服务器端和客户端命令
    • 17.4 数据库操作
      • 17.4.1 string类型
      • 17.4.2 键命令
      • 17.4.3 hash类型
      • 17.4.4 list类型
      • 17.4.5 set类型
      • 17.4.6 zset类型
    • 17.5 centos7安装redis-5.0.0主从
    • 17.6 redis5.0.4集群搭建步骤
    • 17.7 centos7安装升级ruby
    • 17.8 redis-dump方式导入导出数据
    • redis面试题
  • 18 HTML
    • 18.1 html入门
  • 19 CSS
    • 19.1 列表、div、span
    • 19.2 整体感知、常见属性、选择器
    • 19.3 css性质、盒模型、标准文档流、浮动
    • 19.4 行高和字号、超链接美化、background
    • 19.5 相对定位、绝对定位、固定定位、z-index
  • 20 jenkins
    • 20.1 jenkins的安装配置
    • 20.2 Jenkins Job迁移
    • Jenkins常见问题
    • Jenkins关闭和重启的实现方式
    • 使用Jenkins Pipeline自动化构建发布
  • 21 vim
    • vim设置参数以方便YAML语句录入
    • vim常用命令
  • 22 网络
    • TCP协议三次握手四次挥手
    • ip地址
  • 23 未分类
    • DEV SIT UAT PET SIM PRD PROD常见环境英文缩写含义
    • 调优
    • 编程语言的分类
    • curl方式执行shell脚本时如何传参
    • 中文技术文档写作规范
    • PrometheusAlert
  • 24 常见问题
    • centos时区
    • curl https报错
    • du-df磁盘使用不一致
    • linux下增加交换分区
    • mysql最大连接数
  • 25 openldap
    • Centos7 搭建openldap
    • phpldapadmin操作指导
  • 26 gitlab
    • gitlab备份迁移升级&集成openldap
  • 27 监控体系
    • 聊头监控体系
    • grafana问题
  • git
    • git的基本使用
    • Commit message 和 Change log 编写指南
Powered by GitBook
On this page

Was this helpful?

  1. Shell
  2. 第五部分 高级话题

34. 陷阱

# 第34章 陷阱

Turandot: Gli enigmi sono tre, la morte una! Caleph: No, no! Gli enigmi sono tre, una la vita!

——Puccini

以下的做法(非推荐)将让你原本平淡无奇的生活激动不已。

  • 将保留字或特殊字符声明为变量名。

case=value0       # 引发错误。
23skidoo=value1   # 也会引发错误。
# 以数字开头的变量名是被shell保留使用的。
# 试试_23skidoo=value1。以下划线开头的变量名就没问题.

# 然而 . . .   只用一个下划线作为变量名就不行。
_=25
echo $_           # $_是一个特殊变量, 代表最后一个命令的最后一个参数。
# 但是,_是一个有效的函数名!

xyz((!*=value2    # 引起严重的错误。
# Bash3.0之后,标点不能出现在变量名中。
  • 使用连字符或其他保留字符来做变量名(或函数名)。

var-1=23
# 用 'var_1 代替。

function-whatever ()   # 错误
# 用 ‘function_whatever ()’ 代替。


# Bash3.0之后,标点不能出现在函数名中。
function.whatever ()   # 错误
# 用 ‘functionWhatever ()’ 代替。
  • 让变量名与函数名相同。 这会使得脚本的可读性变得很差。

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

do_something=do_something

do_something do_something

# 这么做是合法的,但会让人混淆。
var1 = 23   # ‘var1=23’才是正确的。
# 对于上边这一行来说,Bash会把“var1”当作命令来执行,
# “=”和“23”会被看作“命令”“var1”的参数。

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 ]] 也行。
{ ls -l; df; echo "Done." }
# bash: syntax error: unexpected end of file

{ ls -l; df; echo "Done."; }
#                        ^     ### 最后的这条命令必须以分号结尾。
  • 假定未被初始化的变量(赋值前的变量)被“清0”。事实上,未初始化的变量值为“null”,而不是0。

#!/bin/bash

echo "uninitialized_var = $uninitialized_var"
# uninitialized_var =

# 但是 . . .
# if $BASH_VERSION ≥ 4.2; then

if [[ ! -v uninitialized_var ]]
then
  uninitialized_var=0   # Initialize it to zero!
fi
  • 混淆测试符号=和-ep。请记住,=用于比较字符变量,而-ep用来比较整数。

if [ "$a" = 273 ]      # $a是整数还是字符串?
if [ "$a" -eq 273 ]    # $a为整数。

# 有些情况下,即使你混用-ep和=,也不会产生错误的结果。
# 然而 . . .


a=273.0   # 不是一个整数。

if [ "$a" = 273 ]
then
  echo "Comparison works."
else  
  echo "Comparison does not work."
fi    # Comparison does not work.

# 与a=" 273"和a="0273"相同。


# 类似的, 如果对非整数值使用“-ep”的话,就会产生问题。

if [ "$a" -eq 273.0 ]
then
  echo "a = $a"
fi  # 产生了错误消息而退出。
# test.sh: [: 273.0: integer expression expected

样例 34-1. 数字比较与字符串比较并不相同

#!/bin/bash
# bad-op.sh: 尝试一下对整数使用字符串比较。

echo
number=1

#  下面的"while循环"有两个过错误:
#+ 一个比较明显,而另一个比较隐蔽。

while [ "$number" < 5 ]    # 错!应该是:  while [ "$number" -lt 5 ]
do
  echo -n "$number "
  let "number += 1"
done  
#  如果试图运行这个错误的脚本,就会得到一个错误信息:
#+ bad-op.sh: line 10: 5: No such file or directory
#  在单中括号结构([ ])中,"<"必须被转义,
#+ 即便如此,比较两个整数仍是错误的。

echo "---------------------"

while [ "$number" \< 5 ]    #  1 2 3 4
do                          #
  echo -n "$number "        #  看起来好像可以工作,但是 . . .
  let "number += 1"         #+ 事实上是比较ASCII码,
  done                      #+ 而不是整数比较。

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

# 这么做会产生问题。比如:

lesser=5
greater=105

if [ "$greater" \< "$lesser" ]
then
  echo "$greater is less than $lesser"
fi                          # 105 is less than 5
#  事实上,在字符串比较中(按照ASCII码的顺序)
#+ "105"小于"5"。

echo

exit 0
let "a = hello, you"
echo "$a"   # 0
  • 脚本中的命令可能会因为脚本宿主不具备相应的运行权限而导致运行失败。如果用户在命令行中不能调用这个命令的话,那么即使把它放到脚本中来运行,也还是会失败。这时可以通过修改命令的属性来解决这个问题,有时候甚至要给它设置suid位(当然, 要以root身份来设置)。

  • 试图使用-作为作为重定向操作符(事实上它不是),通常都会导致令人不快的结果。

command1 2> - | command2
# 试图将command1的错误输出重定向到一个管道中 . . .
# . . . 不会工作。

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

感谢,S.C。
#!/bin/bash

minimum_version=2
# 因为Chet Ramey经常给Bash添加一些新的特征,
# 所以你最好将$minimum_version设置为2.XX,3.XX,或是其他你认为比较合适的值。
E_BAD_VERSION=80

if [ "$BASH_VERSION" \< "$minimum_version" ]
then
  echo "This script works only with Bash, version $minimum or greater."
  echo "Upgrade strongly recommended."
  exit $E_BAD_VERSION
fi

...
var=1 && ((--var)) && echo $var
#        ^^^^^^^^^ 在这里,这个与列表返回错误代码1而终止。
#                     不会打印$var的值!
echo $?   # 1
  • 一个带有DOS风格换行符(\r\n)的脚本将会运行失败,因为#!/bin/bash\r\n是不合法的,与我们所期望的#!/bin/bash\n不同,解决办法就是将这个脚本转换为UNIX风格的换行符。

#!/bin/bash

echo "Here"

unix2dos $0    # 脚本先将自己改为DOS格式。
chmod 755 $0   # 更改可执行权限。
               # 'unix2dos'会删除可执行权限

./$0           # 脚本尝试再次运行自己。
               # 但它作为一个DOS文件,已经不能运行了。

echo "There"

exit 0
  • 以#!/bin/sh开头的Bash脚本,不能在完整的Bash兼容模式下运行。某些Bash特定的功能可能会被禁用。如果脚本需要完整的访问所有Bash专有扩展,那么它需要使用#!/bin/bash作为开头。

add2 ()
{
  echo "Whatever ... "   # 删掉zhehan
  let "retval = $1 + $2"
    echo $retval
    }

    num1=12
    num2=43
    echo "Sum of $num1 and $num2 = $(add2 $num1 $num2)"

#   Sum of 12 and 43 = Whatever ... 
#   55

#        这些echo连在一起了。
WHATEVER=/home/bozo
export WHATEVER
exit 0
bash$ echo $WHATEVER
bash$
  • 可以确定的是,即使回到命令行提示符,变量$WHATEVER仍然没有被设置。

样例 34-2. 子shell缺陷

#!/bin/bash
# 子shell中的变量缺陷。

outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo

(
# 开始子shell

echo "outer_variable inside subshell = $outer_variable"
inner_variable=inner  # Set
echo "inner_variable inside subshell = $inner_variable"
outer_variable=inner  # 会修改全局变量吗?
echo "outer_variable inside subshell = $outer_variable"

# 如果将变量‘导出’会产生不同的结果么?
#    export inner_variable
#    export outer_variable
# 试试看。

# 结束子shell
)

echo
echo "inner_variable outside subshell = $inner_variable"  # Unset.
echo "outer_variable outside subshell = $outer_variable"  # Unchanged.
echo

exit 0

# 如果你去掉第19和第20行的注释会怎样?
# 会产生不同的结果吗?

样例 34-3. 将echo的输出通过管道传递给read命令

#!/bin/bash
#  badread.sh:
#  尝试使用'echo'和'read'命令
#+ 非交互的给变量赋值。

#   shopt -s lastpipe

a=aaa
b=bbb
c=ccc

echo "one two three" | read a b c
# 尝试重新给变量a,b,和c赋值。

echo
echo "a = $a"  # a = aaa
echo "b = $b"  # b = bbb
echo "c = $c"  # c = ccc
# 重新赋值失败。

### 但如果 . . .
##  去掉第6行的注释:
#   shopt -s lastpipe
##+ 就能解决这个问题!
### 这是Bash 4.2版本的新特性。

# ------------------------------

# 试试下边这种方法。

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"; echo "b = $b"; echo "c = $c" )
# a = one
# b = two
# c = three
echo "-----------------"
echo "Outside subshell: "
echo "a = $a"  # a = aaa
echo "b = $b"  # b = bbb
echo "c = $c"  # c = ccc
echo

exit 0

事实上,也正如Anthony Richardson指出的那样,通过管道将输出传递到任何循环中, 都会引起类似的问题。

# 循环的管道问题。
#  这个例子由Anthony Richardson编写,
#+ 由Wilbert Berendsen补遗。


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 = false ]
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 = false ]
then
   echo "No files need archiving."
fi

# ==================这里是另一种方法==================

#  将脚本中读取变量的部分放到一个代码块中,
#+ 这样一来,它们就能在相同的子shell中共享了。
#  感谢,W.B。

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 /var/log/messages | grep "$ERROR_MSG" >> error.log
#  “error.log”文件将不会写入任何东西。
#  正如Samuli Kaipiainen指出的那样,
#+ 这一结果是从grep的缓冲区输出的。
#  解决的办法就是把“--line-buffered”参数添加到grep中。
  • 在脚本中使用“suid”命令是非常危险的,因为这会危及系统安全。

  • 使用shell脚本来编写CGI程序是值得商榷的。因为Shell脚本的变量不是“类型安全”的,当CGI被关联的时候,可能会产生令人不快的行为。此外,它还很难抵挡住“破解的考验”。

危险正在接近你 -- 小心,小心,小心,小心。 许多勇敢的心都在沉睡。 所以一定要小心 -- 小心。

——A.J. Lamb and H.W. Petrie

注意事项

Previous33. 选项Next36. 其他

Last updated 5 years ago

Was this helpful?

不合时宜的使用。与其他编程语言相比,Bash非常讲究空白符的使用。

在中,最后一条命令没有以结尾。

误用了操作符。

试图用来设置字符串变量。

有时候在“test”中括号([ ])结构里的变量需要被引用起来(双引号)。如果不这么做的话,可能会引起不可预料的结果。请参考,,。

,用双引号引用一个包含空白符的变量。 有些情况下,这会产生。

使用的功能,可以在产生错误信息的时候,引发修复动作。但是比较老的Linux机器默认安装的可能是Bash 1.XX。

在非Linux机器上的脚本( #!/bin/sh )中使用Bash特有的功能,。Linux系统通常都会把bash别名化为sh,但是在一般的UNIX机器上却不一定会这么做。

使用Bash未文档化的特征,将是一种危险的举动。本书之前的几个版本就依赖一个这种“特征”,下面说明一下这个“特征”,虽然或所能返回的最大正值为255,但是并没有限制我们使用负整数。不幸的是, Bash 2.05b之后的版本,这个漏洞消失了。请参考。

在某些情况下,会返回一个误导性的。或时,就有可能发生这种情况。

不等同于一个错误代码。

如果在中,的话,将会导致脚本的异常行为。

在一个的函数中放置了不止一个echo语句。

这是的。

脚本不能将变量export到它的(即调用这个脚本的shell),或父进程的环境中。就好比我们在生物学中所学到的那样,子进程只会继承父进程, 反过来则不行。

在中设置和操作变量之后,如果尝试在子shell作用域之外使用同名变量的话, 将会产生令人不快的结果。

将echo的输出通过传递给命令可能会产生不可预料的结果。在这种情况下,read命令的行为就好像它在子shell中运行一样。可以使用命令来代替(就好像一样)。

一个相关的问题:当你尝试将tail -f的stdout通过管道传递给时,会产生问题。

Bash不能正确地处理。

在Linux或BSD上编写的Bash脚本,可能需要修改一下,才能使它们运行在商业的UNIX机器上。这些脚本通常都使用GNU命令和过滤工具,GNU工具通常都比一般的UNIX上的同类工具更加强大。这方面的一个非常明显的例子就是,文本处理工具。

遗憾的是,更新Bash本身就会破坏的脚本。让我们回顾一下。

空白符
大括号包含的代码块
分号
字符串比较
let
例子 7-6
例子 16-5
例子 9-6
为防分隔
意想不到的后果
Bash 2.0或更高版本
Bourne shell
可能会引起不可预料的行为
exit
return
例子 24-9
退出状态
设置一个函数内的局部变量
分配一个算术值给一个变量
算术表达式的退出状态
here document
结尾的limit string之前加上空白字符
输出被捕获
行不通
父进程
子shell
管道
read
set
例子15-18
grep
双斜线(//)字符串
tr
过去工作完全正常
使用无正式文件的Bash功能有多危险