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
  • 内建变量
  • $BASH
  • $BASH_ENV
  • $BASH_SUBSHELL
  • $BASHPID
  • $BASH_VERSINFO[n]
  • $BASH_VERSION
  • $CDPATH
  • $DIRSTACK
  • $EDITOR
  • $EUID
  • $FUNCNAME
  • $GLOBIGNORE
  • $GROUPS
  • $HOME
  • $HOMENAME
  • $HOSTTYPE
  • $IFS
  • $IGNOREEOF
  • $LC_COLLATE
  • $LC_CTYPE
  • $LINENO
  • $MACHTYPE
  • $OLDPWD
  • $OSTYPE
  • $PATH
  • $PIPESTATUS
  • $PPID
  • $PROMPT_COMMAND
  • $PS1
  • $PS2
  • $PS3
  • $PS4
  • $PWD
  • $REPLY
  • $SECONDS
  • $SHELLOPTS
  • $SHLVL
  • $TMOUT
  • $UID
  • 位置参数
  • $0, $1, $2 等
  • $#
  • $*
  • $@
  • 其他特殊参数
  • $-
  • $!
  • $_
  • $?
  • $$
  • 注记

Was this helpful?

  1. Shell
  2. 第三部分 shell进阶
  3. 9. 换个角度看变量

9.1 内部变量

Previous9. 换个角度看变量Next9.2 变量类型标注:declare 与 typeset

Last updated 5 years ago

Was this helpful?

内建变量

影响 Bash 脚本行为的变量。

$BASH

Bash程序的路径。

bash$ echo $BASH
/bin/bash

$BASH_ENV

这个环境变量会指向一个 Bash 启动文件,该文件在脚本被调用时会被读取。

$BASH_SUBSHELL

该变量用于提示所处的 subshell 层级。这是在 Bash version 3 中被引入的新特性。

具体用法可以参考 。

$BASHPID

当前 Bash 进程实例的进程ID号。虽然与 $$ 变量不一样,但是通常它们会给出相同的结果。

bash4$ echo $$
11015


bash4$ echo $BASHPID
11015


bash4$ ps ax | grep bash4
11015 pts/2    R      0:00 bash4

然而...

#!/bin/bash4

echo "\$\$ outside of subshell = $$"                              # 9602
echo "\$BASH_SUBSHELL  outside of subshell = $BASH_SUBSHELL"      # 0
echo "\$BASHPID outside of subshell = $BASHPID"                   # 9602

echo

( echo "\$\$ inside of subshell = $$"                             # 9602
  echo "\$BASH_SUBSHELL inside of subshell = $BASH_SUBSHELL"      # 1
  echo "\$BASHPID inside of subshell = $BASHPID" )                # 9603
  #  注意 $$ 总是返回父进程的 PID。

$BASH_VERSINFO[n]

这是一个6个元素的数组,其中包含了已经安装的 Bash 的版本信息。该变量与变量 $BASH_VERSION 类似,但是更加详细。

# Bash 版本信息:

for n in 0 1 2 3 4 5
do
  echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done

# BASH_VERSINFO[0] = 3                      # 主版本号
# BASH_VERSINFO[1] = 00                     # 次版本号
# BASH_VERSINFO[2] = 14                     # 补丁号
# BASH_VERSINFO[3] = 1                      # 构建版本号
# BASH_VERSINFO[4] = release                # 发行状态
# BASH_VERSINFO[5] = i386-redhat-linux-gnu  # 架构
                                            # (与 $MACHTYPE 相同)

$BASH_VERSION

已经安装的 Bash 的版本信息。

bash$ echo $BASH_VERSION
3.2.25(1)-release
tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.

利用 $BASH_VERSION 来判断运行的是哪个 shell 是一个不错的方法,因为变量 $SHELL 并不总是能够给出正确的答案。

$CDPATH

变量指定 cd 命令可以搜索的路径,路径之间用冒号进行分隔。该变量的功能类似于指定可执行文件搜索路径的变量 $PATH。可以在本地文件 ~/.bashrc 中设置该变量。

bash$ cd bash-doc
bash: cd : bash-doc: No such file or directory


bash$ CDPATH=/usr/share/doc
bash$ cd bash-doc
/usr/share/doc/bash-doc


bash$ echo $PWD
/usr/share/doc/bash-doc

$DIRSTACK

指代目录栈中顶部的值,目录栈由命令 pushd 和 popd 控制。

该变量相当于命令 dirs,但是 dirs 命令会显示整个目录栈。

$EDITOR

脚本所调用的默认编辑器,通常是 vi 或是 emcas。

$EUID

有效用户ID。

有效用户ID(EUID)是指当前用户正在使用的用户ID,可以通过 su 命令修改。

$EUID 与 $UID 并不总是相同的。

$FUNCNAME

当前运行函数的函数名。

xyz23 ()
{
  echo "$FUNCNAME now executing."  # xyz2 now executing.
}

xyz23

echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
                                   # 如果在函数外则为空值。

$GLOBIGNORE

$GROUPS

当前用户所属的用户组。

该变量存储了当前用户所归属的用户组ID列表,是一个数组。内容与记录在文件 /etc/passwd 和文件 /etc/group 中的一致。

root# echo $GROUPS
0


root# echo ${GROUPS[1]}
1


root# echo ${GROUPS[5]}
6

$HOME

$HOMENAME

$HOSTTYPE

主机类型。

bash$ echo $HOSTTYPE
i686

$IFS

内部字段分隔符。

bash$ echo "$IFS"

(当 $IFS 设置为缺省值时,显示空行。)


bash$ echo "$IFS" | cat -vte
 ^I$
 $
(显示空白符:首先是一个空格,然后是 ^I [水平制表符],
 然后是换行符,最后在末尾显示 "$"。)


bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
w:x:y:z
(从字符串中解析命令,然后将命令参数分配给位置参数。)

通过设置 $IFS 来忽略文件路径名中空格带来的影响。

IFS="$(printf '\n\t')"   # 按 David Wheeler 所述。

相比于其他字符,变量 $IFS 在处理空白符时有所不同。

样例 9-1. $IFS 与空白符

#!/bin/bash
# ifs.sh


var1="a+b+c"
var2="d-e-f"
var3="g,h,i"

IFS=+
# 加号会被解析成分隔符。
echo $var1     # a b c
echo $var2     # d-e-f
echo $var3     # g,h,i

echo

IFS="-"
# 恢复对加号的默认解析。
# 现在减号会被解析成分隔符。
echo $var1     # a+b+c
echo $var2     # d e f
echo $var3     # g,h,i

echo

IFS=","
# 现在逗号会被解析成分隔符。
# 恢复对减号的默认解析。
echo $var1     # a+b+c
echo $var2     # d-e-f
echo $var3     # g h i

echo

IFS=" "
# 现在空格会被解析成分隔符。
# 逗号恢复成默认解析。
echo $var1     # a+b+c
echo $var2     # d-e-f
echo $var3     # g,h,i

# ======================================================== #

# 然而...
# $IFS 处理空白符的方式不同其他字符。

output_args_one_per_line()
{
  for arg
  do
    echo "[$arg]"
  done #  ^    ^   为了获得更好的视觉体验,把参数放到了括号里。
}

echo; echo "IFS=\"  \""
echo "-------"

IFS=" "
var=" a  b c   "
#    ^ ^^   ^^^
output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
# [a]
# [b]
# [c]


echo; echo "IFS=:"
echo "-----"

IFS=:
var=":a::b:c:::"               # 与上面一样的模式,
#    ^ ^^   ^^^                #+ 仅仅是将 " " 替换成了 ":" ...
output_args_one_per_line $var
# []
# [a]
# []
# [b]
# [c]
# []
# []

# 注意那些“空的”括号。
# 同样的情况也会出现在 awk 命令所使用的 "FS" 字段分隔符中。


echo

exit

(非常感谢 Stéphane Chazelas 提供了上面的样例并做出的详细说明。)

$IGNOREEOF

忽略 EOF:用于指示 Shell 在注销前需要忽略多少个文件结束符(EOF,contrl-D)。

$LC_COLLATE

在 Bash 2.05 版本之后,文件名匹配在不再区分中括号中字母的大小写。例如 ls [A-M]* 将会同时匹配 File1.txt 和 file1.txt 两个文件。如果想要恢复成之前的模式,则需要在文件 /etc/profile 或文件 ~/.bashrc 中通过语句 export LC_COLLATE=C 设置 LC_COLLATE 的值为 C。

$LC_CTYPE

$LINENO

该变量记录了其在脚本中被使用时所处行的行号。该变量只有在被使用时才有意义,在调试过程中非常有用。

# *** 调试部分起始 ***
last_cmd_arg=$_  # 保存最后的命令。

echo "At line number $LINENO, variable \"v1\" = $v1"
echo "Last command argument processed = $last_cmd_arg"
# *** 调试部分终止 ***

$MACHTYPE

设备类型。

识别系统硬件。

bash$ echo $MACHTYPE
i686

$OLDPWD

上一个工作目录(OLD-Print-Working-Directory),也就是之前所在的目录。

$OSTYPE

操作系统类型。

bash$ echo $OSTYPE
linux

$PATH

可执行文件搜索路径,其值通常包含 /usr/bin,/usr/X11R6/bin/,/usr/local/bin 等路径。

bash$ echo $PATH
/bin:/usr/bin:/usr/local/bin/:/usr/X11R6/bin:/sbin:/usr/sbin

PATH=${PATH}:/opt/bin 表示添加目录 /opt/bin 到当前的搜索路径中。在脚本中可以通过这种方式临时添加目录到搜索路径。而当脚本结束时,$PATH 就会恢复到原始值(类似于脚本这样的子进程所作出的修改,不会影响到例如 shell 这样的父进程的环境)。

基于安全考虑,通常在 $PATH 中会省略当前工作目录 ./。

$PIPESTATUS

bash$ echo $PIPESTATUS
0

bash$ ls -al | bogus_command
bash: bogus_command: command not found
bash$ echo ${PIPESTATUS[1]}
127

bash$ ls -al | bogus_command
bash: bogus_command: command not found
bash$ echo $?
127

$PIPESTATUS 数组中的每一个元素都代表了该管道中相对应命令的退出状态。$PIPESTATUS[0] 表示管道中第一个命令的退出状态,$PIPESTATUS[1] 表示第二个命令的退出状态,以此类推。

在Bash 3.0 以下版本的登录shell中,变量 $PIPESTATUS 可能会包含一个不正确的 0 值。

tcsh% bash

bash$ who | grep nobody | sort
bash$ echo ${PIPESTATUS[*]}
0

如果脚本包含了上述代码,应该得到期望的输出是 0 1 0。

感谢 Wayne Pollock 指出了这个问题并提供了上述的样例。

在某些场景下,$PIPESTATUS 变量将会产生非预期结果。

bash$ echo $BASH_VERSION
3.00.14(1)-release

bash$ ls | bogus_command | wc
bash: bogus_command: command not found
 0       0       0

bash$ echo ${PIPESTATUS[@]}
141 127 0

$PIPESTATUS 是一个易失的变量。该变量需要在目标管道执行完成后,且其他任何命令执行之前去捕获。

bash$ ls | bogus_command | wc
bash: bogus_command: command not found
 0       0                0

bash$ echo ${PIPESTATUS[@]}
0 127 0

bash$ echo ${PIPESTATUS[@]}
0

$PPID

一个进程的 $PPID 即该进程的父进程的进程ID(pid)。

$PROMPT_COMMAND

该变量存储在主提示符 $PS1 显示之前所需要执行的命令。

$PS1

主提示符,即在命令行中显示的提示符。

$PS2

次要提示符,当需要额外输入时出现的提示符。默认显示为 >。

$PS3

$PS4

其可以作为调试的辅助手段,把一些诊断信息显示在 $PS4 中可能会有帮助。

P4='$(read time junk < /proc/$$/schedstat; echo "@@@ $time @@@ " )'
# 根据 Erik Brandsberg 提供的建议。
set -x
# 可以在后面写各种命令...

$PWD

工作目录(你当前所在的目录)。

#!/bin/bash

E_WRONG_DIRECTORY=85

clear # 清空屏幕。

TargetDirectory=/home/bozo/projects/GreatAmericanNovel

cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."

if [ "$PWD" != "$TargetDirectory" ]
then    # 小心不要偶然清空了错误的目录。
  echo "Wrong directory!"
  echo "In $PWD, rather than $TargetDirectory!"
  echo "Bailing out!"
  exit $E_WRONG_DIRECTORY
fi

rm -rf *
rm .[A-Za-z0-9]*    # 删除隐藏文件。
# rm -f .[^.]* ..?*   删除那些以多个点开头的文件。
# (shopt -s dotglob; rm -f *)   这样写也可以。
# 感谢 S.C. 提出这点。

#  文件名可以包含ASCII码中范围为 0-255 的所有字符,
#+ 除了字符 "/"。
#  删除以一些特殊字符开头的文件,例如 -
#+ 留作练习。(提示: rm ./-weirdname 或者 rm -- -weirdname)
result=$?   # 删除操作的结果。如果删除成功,值为0。

echo
ls -al              # 是不是还有剩余没有删除的文件?
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo

# 如果有其他需要,在这里完成。

exit $result

$REPLY

#!/bin/bash
# reply.sh

# REPLY 是 'read' 命令的默认接收参数。

echo
echo -n "What is your favorite vegetable? "
read

echo "Your favorite vegetable is $REPLY."
#  当且仅当 'read' 命令没有接收参数的时候,
#+ REPLY 才能保存最近一次 'read' 命令接收的值。

echo
echo -n "What is your favorite fruit? "
read fruit
echo "Your favorite fruit is $fruit."
echo "but..."
echo "Value of \$REPLY is still $REPLY."
#  因为变量 $fruit 接收了新一次 "read" 命令所读入的值,
#+ 所以 $REPLY 仍旧存储的是上一次接收的值。

echo

exit 0

$SECONDS

该变量记录到目前为止脚本执行的时间,单位为秒。

#!/bin/bash

TIME_LIMIT=10
INTERVAL=1

echo
echo "Hit Control-C to exit before $TIME_LIMIT seconds."
echo

while [ "$SECONDS" -le "$TIME_LIMIT" ]
do   #   $SECONDS 是一个 shell 的内部变量。
  if [ "$SECONDS" -eq 1 ]
  then
    units=second
  else
    units=seconds
  fi

  echo "This script has been running $SECONDS $units."
  #  在一台性能较差或负载过重的设备上,
  #+ 这个脚本可能会偶尔跳过几个计数。
  sleep $INTERVAL
done

echo -e "\a"  # 发出蜂鸣声!

exit 0

$SHELLOPTS

bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs

$SHLVL

当前 shell 的层级,即嵌套了多少层 Bash 。如果命令行的层级 $SHLVL 为 1,那么在其中执行的脚本层级则增加到 2。

$TMOUT

如果 $TMOUT 被设为非 0 值 time,那么 shell 会在 $time 秒后超时,然后导致 shell 登出。

在 Bash 2.05b 版本之后,可以在脚本中将 read 命令与 $TMOUT 变量进行结合。

# 只能在 Bash 2.05b 及之后的版本中成功执行。

TMOUT=3    # 提示会在 3 秒后超时。

echo "What is your favorite song?"
echo "Quickly now, you only have $TMOUT seconds to answer!"
read song

if [ -z "$song" ]
then
  song="(no answer)"
  # 默认值。
fi

echo "Your favorite song is $song."

样例 9-2. 限时输入

#!/bin/bash
# timed-input.sh

# TMOUT=3    在新版本的 Bash 中起效。

TIMER_INTERRUPT=14
TIMELIMIT=3  # 在该实例中设置为 3 秒。
             # 同样可以设置成其他值。

PrintAnswer()
{
  if [ "$answer" = TIMEOUT ]
  then
    echo $answer
  else       # 不要混淆了这两个实例。
    echo "Your favorite veggie is $answer"
    kill $!  #  终止在后台运行的
             #+ 不再被需要的 TimerOn 函数。
             #  $! 代表最后一个在后台运行的作业的进程ID。
  fi

}


TimerOn()
{
  sleep $TIMELIMIT && kill -s 14 $$ &
  # 等待 3 秒,然后给脚本发送一个信号。
}


Int14Vector()
{
  answer="TIMEOUT"
  PrintAnswer
  exit $TIMER_INTERRUPT
}

trap Int14Vector $TIMER_INTERRUPT
# 我们的目的就是通过时间中断 (14) 终止程序。

echo "What is your favorite vegetable "
TimerOn
read answer
PrintAnswer


#  必须承认,这个实现限时输入的方法并不优雅。
#  但利用 "read" 命令的 "-t" 选项可以简化这个操作。
#  参考脚本 "t-out.sh"。
#  思考一下,如果不是对用户的单次输入时间进行限制,
#+ 而是对整个脚本的运行时间进行限制,应该怎么做?

#  如果你需要更优雅的写法 ...
#+ 可以考虑用 C 或者 C++ 来编写应用,
#+ 并使用其中包含的类似 'alarm' 或是 ‘setitimer' 等合适的库函数来实现计时。

exit 0

样例 9-3. 再来一次,限时输入

#!/bin/bash
# timeout.sh

#  该脚本由 Stephane Chazelas 编写,
#+ 并由本书作者修改。

INTERVAL=5                # 超时所需的时间间隔

timedout_read() {
  timeout=$1
  varname=$2
  old_tty_settings=`stty -g`
  stty -icanon min 0 time ${timeout}0
  eval read $varname      # 或者直接写成 read $varname
  stty "$old_tty_settings"
  # 参考 "stty" 的帮助页面 (man)。
}

echo; echo -n "What's your name? Quick! "
timedout_read $INTERVAL your_name

#  该脚本也许并不能在所有类型的终端上正常运行。
#  最大的超时时间间隔依赖于终端。
#+ (通常是 25.5 秒)。

echo

if [ ! -z "$your_name" ]  # 如果在超时前输入了姓名 ...
then
  echo "Your name is $your_name."
else
  echo "Timed out."
fi

echo

# 该脚本的计时行为与 "timed-input.sh" 中的计时行为有所不同,
# 该脚本的计时器会在每次按键后被重置。

exit 0

样例 9-4. 限时 read

#!/bin/bash
# t-out.sh [time-out]
# 从 "syngin seven" 的建议中所汲取的灵感,谢谢你们。


TIMELIMIT=4         # 4 秒

read -t $TIMELIMIT variable <&1
#                           ^^^
#  在这个实例中,只有 Bash 1.x 或 Bash 2.x 版本需要 "<&1",
#  而在 Bash 3 及更高版本则不需要。

echo

if [ -z "$variable" ]  # 判断是否为空。
then
  echo "Timed out, variable still unset."
else
  echo "variable = $variable"
fi

exit 0

$UID

用户 ID。

样例 9-5. 我是 root 用户吗?

#!/bin/bash
# am-i-root.sh:   我是否是 root 用户?

ROOT_UID=0   # Root 用户的 $UID 为 0。

if [ "$UID" -eq "$ROOT_UID" ]  # 只有真正的 "root" 用户才能经受得住考研。
then
  echo "You are root."
else
  echo "You are just an ordinary user (but mom loves you just the same)."
fi

exit 0


# ============================================================= #
# 下面的代码将不会被执行,因为脚本已经退出了。

# 另外一种判断是否是 root 用户的方法:

ROOTUSER_NAME=root

username=`id -nu`              # 或是...  username=`whoami`
if [ "$username" = "$ROOTUSER_NAME" ]
then
  echo "Rooty, toot, toot. You are root."
else
  echo "You are just a regular fella."
fi
tcsh% echo $LOGNAME
bozo
tcsh% echo $SHELL
/bin/tcsh
tcsh% echo $TERM
rxvt

bash$ echo $LOGNAME
bozo
bash$ echo $SHELL
/bin/tcsh
bash$ echo $TERM
rxvt

位置参数

$0, $1, $2 等

$#

$*

将所有的位置参数整合,视作一个单词。

该参数必须是被引用的状态,"$*"。

$@

该参数等同于 $*,但其中每个参数都是独立的被引用的字符串。也就是说,所有的参数都是被原封不动的进行传递,并没有被解析或是扩展。这意味着,参数列表中的每一个参数都被独立视为一个单词。

同样,该参数必须是被引用的状态,"$@"。

样例 9-6. 参数列表:利用 $* 和 $@ 列出参数

#!/bin/bash
# arglist.sh
# 在调用该脚本时需要跟上一些参数,例如 "one two three" ...

E_BADARGS=85

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` argument1 argument2 etc."
  exit $E_BADARGS
fi

echo

index=1          # 初始化计数器。

echo "Listing args with \"\$*\":"
for arg in "$*"  # 如果这里没有引用 "$*",脚本将不会正常运行。
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $* 将所有参数视作一个单词。
echo "Entire arg list seen as single word."

echo

index=1          # 重置计数器。
                 # 如果忘了这一步将会发生什么?

echo "Listing args with \"\$@\":"
for arg in "$@"
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $@ 将所有参数视作独立的单词。
echo "Arg list seen as separate words."

echo

index=1          # 重置计数器。

echo "Listing args with \$* (unquoted):"
for arg in $*
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # 未被引用的 $* 将所有参数视作独立的单词。
echo "Arg list seen as separate words."

exit 0

在 shift 命令执行后,$@ 将会保留除了 $1 之外的剩余的命令行参数,而 $1 则会被丢弃。

#!/bin/bash
# 使用 ./scriptname 1 2 3 4 5 调用脚本

echo "$@"    # 1 2 3 4 5
shift
echo "$@"    # 2 3 4 5
shift
echo "$@"    # 3 4 5

# 每一次 "shift" 都会丢弃参数 $1。
# "$@" 则包含了剩余的所有参数。

样例 9-7. $* 和 $@ 的不一致行为

#!/bin/bash

#  Bash 的内部变量 "$*" 和 "$@" 拥有不稳定的行为,
#+ 这些行为是否出现通常依赖于它们是否是被引用的状态。
#  下面的代码会演示在分词和换行时,这些变量所会出现的一些不一致的处理方式。


set -- "First one" "second" "third:one" "" "Fifth: :one"
# 设置脚本参数,$1, $2, $3 等等。

echo 

echo 'IFS unchanged, using "$*"'
c=0
for i in "$*"               # 被引用状态。
do echo "$((c+=1)): [$i]"   # 这一行在下面所有的例子中都保持不变。
                            # 输出参数。
done
echo ---

echo 'IFS unchanged, using $*'
c=0
for i in $*                 # 未被引用状态。
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS unchanged, using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS unchanged, using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

IFS=:
echo 'IFS=":", using "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
echo ---

var=$*
echo 'IFS=":", using "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

var="$*"
echo 'IFS=":", using $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

var=$@
echo 'IFS=":", using $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

var="$@"
echo 'IFS=":", using "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", using $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done

echo

# 尝试在 ksh 或是 zsh -y 下执行这个脚本。

exit 0

#  这个样例是由 Stephane Chazelas 所编写,
#+ 由本书作者轻微改动。

$@ 和 $* 仅在被双引号引用时才会表现出不同。

样例 9-8. 当 $IFS 为空时 $* 和 $@ 的表现

#!/bin/bash

#  如果 $IFS 被设置为空,
#+ 那么 "$*" 和 "$@" 将不会像期望的那样输出位置参数。

mecho ()       # 输出位置参数。
{
echo "$1,$2,$3";
}


IFS=""         # 设置为空。
set a b c      # 位置参数。

mecho "$*"     # abc,,
#                   ^^
mecho $*       # a,b,c

mecho $@       # a,b,c
mecho "$@"     # a,b,c

#  当 $IFS 为空时 $* 和 $@ 的行为
#+ 依赖于 Bash 或是 sh 所运行的版本。
#  因此不宜在脚本中使用这个“特性”。


# 感谢 Stephane Chazelas。

exit

其他特殊参数

$-

$!

LOG=$0.log

COMMAND1="sleep 100"

echo "Logging PIDs background commands for script: $0" >> "$LOG"
# 这样就可以监控命令,并在必要的时候终止它们。
echo >> "$LOG"

# 记录命令。

echo -n "PID of \"$COMMAND1\":  " >> "$LOG"
${COMMAND1} &
echo $! >> "$LOG"
# "sleep 100" 的 PID 是 1506

# 感谢 Jacques Lederer 提出的该建议。

将 $! 用于控制任务:

possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
# 强制终止一个出错的程序。
# 非常有用,例如可以用在启动脚本中。

# 感谢 Sylvain Fourmanoit 提出的这个利用变量 "$!" 的创造性用法。

也可以这么使用:

# 该样例由 Matthew Sage 编写。
# 本书经授权后使用。

TIMEOUT=30   # 以秒为单位的超时时间值。
count=0

possibly_hanging_job & {
        while ((count < TIMEOUT )); do
                eval '[ ! -d "/proc/$!" ] && ((count = TIMEOUT))'
                # 当前运行进程的详细信息都可以在 /proc 中找到。
                # "-d" 用于测试进程是否存在(即在 /proc 文件夹下该进程的文件夹是否存在)。
                # 我们在等待出问题的任务出现。
                ((count++))
                sleep 1
        done
        eval '[ -d "/proc/$!" ] && kill -15 $!'
        # 如果被挂起的任务正在运行就终止它。
}

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

#  然而,如果另外一个进程在 "hanging_job" 之后开始运行
#+ 该函数可能不能正常运行 ...
#  在那种情况下,一个非我们预期的任务会被终止。
#  Ariel Meragelman 提出了如下的解决方案。

TIMEOUT=30
count=0

possibly_hanging_job & {
    while ((count < TIMEOUT )); do
            eval '[ !-d "/proc/$lastjob" ] && ((count = TIMEOUT))'
            lastjob=$!
            ((count++))
            sleep 1
    done
    eval '[ -d "/proc/$lastjob" ] && kill -15 $lastjob'
}

exit

$_

该变量被设置为上一个执行的命令的最后一个参数。

样例 9-9. 下划线变量

#!/bin/bash

echo $_              #  /bin/bash
                     #  仅通过调用 /bin/bash 执行该脚本。
                     #  注意这个结果会根据脚本如何被调用
                     #+ 而有所不同。

du >/dev/null        #  这样命令就不会在命令行上有任何输出。
echo $_              #  du

ls -al >/dev/null    #  这样命令就不会在命令行上有任何输出。
echo $_              #  -al (最后一个参数)

:
echo $_              #  :

$?

$$

注记

栈寄存器是一段连续的内存空间,在该空间中,存入(压栈)的值是以倒序的方式取出(出栈)的。最后一个存入的值被最先取出。其通常又被称为后进先出(LIFO)或是下堆栈。

当前运行脚本的进程 ID 就是 $$。

术语“变量(argument)”和“参数(parameter)”通常情况下是可以互相交换使用的。在本书中,它们具有相同的含义:传入脚本或函数的变量。

可以参考 。

在时所忽略的文件名模式列表。

当前用户的主目录,其值通常为 /home/username (参考 )。

系统启动的初始化脚本通过命令 hostname 给系统分配主机名。而函数 gethostname() 则是给 Bash 的内部变量 $HOSTNAME 赋值。可以参考 。

类似变量 ,用于识别系统硬件信息。

该变量决定了 Bash 在解析字符串时如何去识别 或单词边界。

$IFS 的缺省值是空白符(空格,制表符以及换行符),但其可以被修改。例如你在处理逗号分隔的文件时可以将其设置为逗号。需要注意 使用保存在 $IFS 中的第一个字符。可以参考 。

也可以参考 , 和 ,获取更多使用 $IFS 的技巧。

经常会在文件 或是文件 /etc/profile 中被设置。该变量控制文件名扩展和模式匹配中的排序顺序。如果设置不得当,LC_COLLATE 将会导致 中出现非预期结果。

这个内部变量控制在 和模式匹配中的字符解析行为。

给定一个命令,shell就会自动从搜索路径包含的目录中利用哈希表搜索该可执行命令。而搜索路径就保存在 $PATH 中,其中包含的一系列目录则通过冒号进行分隔。通常情况下,$PATH 会定义在文件 /etc/profile 或文件 中(参考 )。

该 变量保存了最后运行的前台 的 。

Chet Ramey 把上述非预期结果的原因归咎于 命令的行为。如果 ls 将结果输出到没有被读取的管道上,产生的 SIGPIPE 信号将会终止 ls 命令,同时其 从期望的 0 变为 141。而同样的情况也会发生在命令 tr 中。

在 $PIPESTATUS 不能给出所期望的信息的情况下,使用 可能会有帮助。

可以与命令 进行比较。

三级提示符,显示在 select 循环中(参考 )。

四级提示符,当使用 -x [verbose trace] 调用脚本时显示的提示符。默认显示为 +。

该变量是内建命令 的翻版。

当没有给 命令提供接收参数时的默认接收参数。该变量同样适用于 菜单接收用户输入值的场景,需要注意的是,用户只需要输入菜单项的编号,而不需要输入完整的菜单项内容。

该只读变量记录了 shell 中已启用的 列表。

该变量 。当你需要指出嵌套了多少层 subshell 时,需要使用变量 。

在脚本中,同样也存在其他一些实现超时功能的更复杂的方法。其中一个方法是设置一个循环的计时器,当脚本超时的时候,计时器会给脚本发送一个信号。同时,也需要一个处理信号的程序来 (参考 )由循环计时器产生的中断。

还有一种方法是使用 。

可能最简单的方法就是利用 命令的 -t 选项。

记录在文件 中当前用户的用户标识号。

该 ID 表示的是当前用户的真实 ID,即使用户通过 su 命令临时切换至另一个用户,这个 ID 也不会改变。$UID 是一个只读变量,不能够被命令行或是脚本中的命令所修改,并与内建命令 相对应。

还可以参考 。

变量 $ENV,$LOGNAME,$MAIL,$TERM,$USER 以及 $USERNAME 并不是 Bash 的 ,而是在 或系统的某个启动文件中,被设置而成的 。代表当前用户登录 shell 名称的变量 $SHELL 是在文件 /etc/password 或是某个初始化脚本中被设定的,它也不是一个 Bash 的内建变量。

位置参数。出现在从命令行传递给脚本、函数或是通过内建命令 设置变量时(参考 或是 )。

命令行参数或是位置参数的个数(参考 )。

参数 $@ 也可被用作过滤 shell 脚本输入的工具。结构 cat "$@" 可以接受来自标准输入 stdin 的输入,也可以接受传递给脚本的参数中的文件中的输入。参考 和 。

根据分隔符 设置的不同,$* 和 $@ 有时会出现不一致或非预期行为。

使用 命令设置的脚本标记。参考 。

这个参数最开始是从 ksh 引入到 Bash中的。但很遗憾的是,该参数在 Bash 脚本中并不能可靠地运行。该参数可能的一个用法是用于 。

运行在后台的最后一个任务的 。

命令、 或是脚本自身的 。参考 。

脚本自身的进程 ID。该变量 $$ 通常在脚本构建独有的临时文件时被使用(参考 ,,以及 )。该方法通常比调用 命令更简单。

类似于 。在本文中,嵌套是指代一种模式被嵌入在一种更大的模式中。在 1913 年出版的韦伯斯特大辞典中用一种更加优雅的方式解释了什么是嵌套:“一组按体积大小排列的盒子、箱子或是类似的东西,它们中的每一个都被放入到另一个更大的箱子中。(A collection of boxes, cases, or the like, of graduated size, each put within the one next larger.)”。

在 subshell 中运行的脚本,$$ 而非 subshell 的。

样例21-1
样例 A-50
文件匹配
样例 10-7
样例 10-7
$MACHTYPE
字段
$*
样例 5-1
样例 16-41
样例 11-8
样例19-14
.bashrc
文件名匹配
文件匹配
环境变量
~/.bashrc
附录 H
数组
管道
退出状态(es)
ls
退出状态
pipeline 选项
pidof
样例 11-30
选项
pwd
read
select
选项
捕获
样例 32-5
stty
read
/etc/passwd
id
样例2-3
内建变量
Bash
环境变量
set
样例 4-5
样例 15-16
样例 36-2
样例 16-24
样例 16-25
$IFS
set
样例 15-16
自检脚本是否可交互
进程ID
函数
退出状态
样例 24-7
样例 32-6
样例 16-31
样例 15-27
mktemp
递归
返回脚本的进程 ID
不受 subshell 影响
$BASH_SUBSHELL