Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Bash学习笔记

15
Mar
2008

Bash学习笔记

By Alex
/ in Linux
/ tags Bash, Command, 学习笔记, 文本处理
0 Comments
Bash简介

在Linux系统中,作为/bin/sh的标准Shell是GNU工具集中的bash(GNU Bourne-Again Shell),大多数Linux发行版中/bin/sh是指向/bin/bash的一个链接。

单实例运行
基于PID进行控制

Bash编程中,可以通过PID文件进行简单的单例控制——仅仅同时仅仅允许脚本的单个实例在运行:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env bash
PIDFILE=/var/run/gmem-sync.pid
if [ -f $PIDFILE ]
then
  # PID文件存在
  PID=$(cat $PIDFILE)
  ps -p $PID > /dev/null 2>&1
  if [ $? -eq 0 ]
  then
    # PID文件指定的进程存在
    echo "Job is already running"
    exit 1
  else
    # PID文件指定的进程不存在
    echo $$ > $PIDFILE
    if [ $? -ne 0 ]
    then
      echo "Could not create PID file"
      exit 1
    fi
  fi
else
  echo $$ > $PIDFILE
  if [ $? -ne 0 ]
  then
    echo "Could not create PID file"
    exit 1
  fi
fi
 
# 这里编写主逻辑
 
 
# 完毕后删除PID文件
rm $PIDFILE
使用flock命令
Shell
1
2
3
# 以独占锁(-x)打开gmem-sync.pid文件,并执行-c指定的命令
# -n 如果无法获得独占锁,立即静默的退出,退出状态非0
flock -xn /var/run/gmem-sync.pid -c /usr/local/bin/gmem-sync.sh
色彩控制

我们可以对终端输出的字符颜色、背景色等参数进行设置,实现方式是转义序列。这个控制实际上与Bash语言无关,其它语言在输出时也可以使用。

转义序列以ESC字符开头,对应八进制的\033。控制代码格式为:

1
        \033[显示方式;前景色;背景色;动作m;

注意:显示方式、前景色、背景色都是可选的,而且顺序可以颠倒。示例:

Shell
1
2
3
4
# 恢复系统默认设置
\033[0m
# 设置蓝色背景、白色前景、光标闪烁
\033[44;37;5m

在脚本中使用色彩控制的例子:

Shell
1
echo -e "\033[44mDumping Gmem database on hk.gmem.cc ...\033[0m"
编码列表
编码 动作 | 颜色 编码 动作 | 颜色
0 恢复默认设置 36 青色前景
1 启用粗体 37 白色前景
2 启用一半亮度 38 默认前景+下划线
4 启用下划线 39 默认前景+取消下划线
5 启用闪烁 40 黑色背景
7 前背景色交换 41 红色背景
22 禁用粗体 42 绿色背景
24 禁用下划线 43 棕色背景
27 禁用前背景色交换 44 蓝色背景
30 黑色前景 45 紫色背景
31 红色前景 46 青色背景
32 绿色前景 47 白色背景
33 棕色前景 49 黑色背景
34 蓝色前景    
35 紫色前景    
系统日志

可以利用初始化脚本函数(Init Script Functions)来输出系统日志:

函数 说明
log_daemon_msg 输出一般性的daemon日志
log_end_msg 指定调用参数0:表示成功,1表示失败
log_success_msg 输出成功消息到未指定的日志文件(unspecified log file)中,消息格式不指定。消息应该相对较短,最好不超过60字符
log_failure_msg 输出失败消息到未指定的日志文件(unspecified log file)中,消息格式不指定。消息应该相对较短,最好不超过60字符
log_warning_msg 输出警告消息到未指定的日志文件(unspecified log file)中,消息格式不指定。消息应该相对较短,最好不超过60字符
特殊字符
字符  说明 
#

单行注释

;

命令分隔符,可以在单行放置多个命令:

Shell
1
2
echo hello; echo there
if [ -x "$filename" ]; then # 注意分号后面的空格
;;

CASE子句终止符:

Shell
1
2
3
4
case "$variable" in
    abc) echo "\$variable = abc" ;;
    xyz) echo "\$variable = xyz" ;;
esac
;;&、;& Bash4+的CASE子句终止符
. 点号命令,相当于source命令
. 作为文件名的组成部分:隐藏文件的前缀
. 正则式字符串匹配,匹配任何单字符
" 部分引用,保留字符串中的大部分字符(继续重新解释:$、\、`),用于括起字符串,如果其中有特殊字符
' 完全引用,保留字符串中的全部字符(无法表示'字符)
,

串联多个表达式,最后一个作为返回值:

Shell
1
let "t2 = ((a = 9, 15 / 3))" #设置t2=15/3,a=9
, ,, 参数替换时的小写转换,双字符表示所有字符都转换,单字符表示只转换第一个字符(逗号)
\

转义,后面的字符按字面解析(Shell不做特殊解释)。

\n 意味着新的一行
\r 回车
\t Tab键
\v 垂直Tab,查前边的Ctl-K
\b backspace,查前边的Ctl-H
\a 报警响铃
\0xx 转换成8进制ASCII 解码

\

后面跟命令,例如\cp,可以临时禁用alias

/ 文件路径分隔符
` 命令替换,将命令的输出赋值为变量,在子shell中执行
:

空命令,什么都不做,exit status=0(true)

Shell
1
2
3
4
5
6
if condition
then : #什么都不做
else
    take-some-action
fi
: >> target_file #如果不存在,则创建文件
! 反转测试或者exit status的值
* 文件名通配符
* 正则式中,表示任意数量的字符
* 乘号
**

幂

** 扩展文件匹配
? 参数替换中,用于测试变量是否被设置
? 通配符,匹配单个字符
$ 变量替换:表示变量的内容,例如 var1=5 echo $var1
$ 在正则式中表示行的结尾
${} 参数替换
$' ... ' 引用字符串扩展
$*  $@ 位置参数
$? 退出状态码,包含一个命令、函数、脚本的返回值
$$ 持有进程的标识符
() 命令组。在子shell中执行,父shell无法见到其中的变量
() 数组初始化:Array=(element1 element2 element3)
{xx,yy,zz}

花括号扩展:注意花括号中不能出现空格,除非\转义,或者引号包含之

Shell
1
2
3
4
5
6
echo \"{These,words,are,quoted}\"
#打印:"These" "words" "are" "quoted"
cat {file1,file2,file3} > combined_file
#将三个文件的内容连接合并到combined_file
cp file22.{txt,backup}
#复制file22.txt为file22.backup
{a..z}

扩展的花括号扩展:

Shell
1
2
3
4
5
6
echo {a..z}
#打印 a b c d e f g h i j k l m n o p q r s t u v w x y z
echo {0..3}
#打印 0 1 2 3
base64_charset=( {A..Z} {a..z} {0..9} + / = )
#初始化一个数组
{}

代码块,亦称内联组(inline group),创建一个匿名函数,但是其内部变量对外部可见。不会发起子Shell

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
File=/etc/fstab
{
    read line1
    read line2
} < $File
echo "第一行:"
echo "$line1"
echo
echo "第二行:"
echo "$line2"
{
    echo "$line1"
} > $File
exit 0
[] 测试其中的表达式
[[ ]] 测试其中的表达式,比[]更灵活
[ ] 数组元素:Array[1]=slot_1 echo ${Array[1]}
[ ] 在正则式中表示一个范围内的字符
$[ ... ]

(废弃)整数扩展,估算其中的整数值:

Shell
1
2
3
4
a=3
b=7
echo $[$a+$b] # 10
echo $[$a*$b] # 21
(( ))

算术扩展,和let 命令很像,允许算术运算和赋值。特别的,在某些场景下,允许使用C风格的语法,举例:

Shell
1
2
3
4
5
6
7
#将a赋值为8
a=(( 5 + 3 ));
#双圆括号也是一种在Bash 中允许使用C 风格的变量处理的机制,注意下面赋值的空格
(( a = 23 ))
(( a++ ))
(( a-- ))
(( t = a<45?7:11 )) # C 风格的3元操作
重定向符

command < filename:命令的标准输入来自文件
command > filename:重定向脚本的标准输出到文件,如果有内容,覆盖
command &> filename:重定向命令的stdout、stderr到文件
command >&2:重定向命令的stdout到stderr
command > file1 2>file2:重新标准输出到file1,标准错误到file2
command > file 2>&1:先重定向stderr到stdout,然后统一重定向到file
scriptname >> filename:附加脚本的标准输出到文件
[i] <> filename 打开文件用于读写,并分配文件描述符,没文件则创建
<< here document中使用的重定向
<<< here string中使用的重定向
>| 强制重定向,强制覆盖文件内容

进程替换 (command)>
<(command)
<, >

ASCII比较

Shell
1
2
3
4
5
6
veg1=carrots
veg2=tomatoes
if [[ "$veg1" < "$veg2" ]]
then :
else :
fi
\<, \> 正则式中表示单词边界
|

管道,把前面命令的stdout作为后一个命令或者shell的stdin

Shell
1
2
#打印所有.lst文件,并排序和删除重复行
cat *.lst | sort | uniq

 管道作为子进程运行,因此不能修改变量

|| 逻辑或,在测试中,导致返回0(成功),只要任何一个表达式为true
& 在后台运行一个任务
&& 逻辑与
- 简写的命令选项的前缀
-- 命令选项前缀
- 上一个工作目录:cd -,相当于$OLDPWD
- 减号
- 很多命令中表示标准输入,例如cat、sed
= 赋值符
+ 加号
+ 命令选项,部分命令使用+表示启用某些特性,-表示禁用某些特性
% 模
~ 家目录
~+ 当前工作目录,相当于$PWD
~- 上一个工作目录:cd -,相当于$OLDPWD
=~ 正则式匹配
^ 正则式中表示行的开始
^ ^^ 参数替换时的大写转换,双字符表示所有字符都转换,单字符表示只转换第一个字符
控制字符 Ctl-A 光标移到行首
Ctl-B 后退符
Ctl-C 终止前端任务
Ctl-D 从Shell注销
Ctl-E 移动光标到行尾
Ctl-F 移动光标向前一字符
Ctl-G 响铃
Ctl-H 擦除字符
Ctl-I 水平制表
Ctl-J 新一行
Ctl-K 垂直制表
Ctl-L 清屏
Ctl-M 回车
Ctl-N 从历史缓冲中删除一行
Ctl-O 新一行
Ctl-P 撤销历史缓冲中最后一条命令
Ctl-Q 恢复终端的stdin
Ctl-R 在历史缓冲中反向查找
Ctl-S 暂停,冻结终端的stdin
Ctl-U 删除光标到行首的所有字符,在某些设置下,删除全行
Ctl-V 当输入字符时,Ctl-V 允许插入控制字符。echo -e '\x0a' 等价于 echo
Ctl-W 删除当前光标到前边的最近一个空格之间的字符
Ctl-Z 终止前台工作
空白符 作为命令、变量之间的分隔符。在某些情况下,例如赋值,空白符是禁止的
变量与参数
基础知识
变量替换

变量的名称是作为其值的占位符,获取其值的操作称为变量替换——使用$前缀执行。在以下情况下,不需要$前缀:变量声明、变量赋值、read、uset、export、((算术表达式))、或者作为代表信号的特殊情况下。在双引号中还是会发生变量替换,这被叫做部分引用,或叫弱引用;在单引号中就不会发生变量替换,这叫做全引用,也叫强引用。

$是${}的简写形式。

变量赋值

未初始化的变量的值为null。使用这样的变量可能出现问题,但是进行算术运算是允许的,相当于0

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
a=879
echo "The value of \"a\" is $a."
let a=16+5 #使用let赋值
#在for循环中赋值
for a in 7 8 9 11
do
    echo -n "$a "
done
#在read 命令状态中赋值
echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a."
#把echo 命令的结果传给变量a
a=`ls -l`
echo $a #注意,ls命令输出的空白符,包括换行,均消失
echo "$a" #空白符保留
# 命令替换的另外一种形式
a=$(cat /ect/redhat-release)
 
# 命令替换嵌套,$()形式嵌套了``形式
pid=$(ps `jcmd | awk '{print $1}' ` | grep passport | awk '{print $1}' | head -1)
变量类型

Bash不区分变量的类型,在本质上所有变量均是字符串。但是在特定上下文下,又作为数字看待。

特殊变量

局部变量:只有在代码块或者函数中可见

环境变量:Shell启动时,创建自己的环境,并影响所有子Shell

位置参数:$0, $1, $2, $3...,其中$0代表脚本文件的名字1-9代表参数${10}代表第10个参数,等等。$*、$@表示所有位置参数的数组。使用shift可以左移参数

参数解析
push.sh
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
 
# 解析传递给此脚本的参数
# -o 指定短参数,单字符,多个参数连续写在一起,结尾:
# -l 指定长参数,多个参数使用逗号分隔,结尾:
# 此命令修改$@,$N的含义可能发生改变,命令的非选项部分,最好放在前面
getopt -o lc: --long latest,chart:  -- "$@" > /dev/null
 
LATEST=false
CHART=""
 
echo '$1'=$1
echo '$2'=$2
echo '$3'=$3
echo '$4'=$4
echo '$5'=$5
 
while true; do
  case "$1" in
    -l | --latest ) LATEST=true; shift ;;
    -c | --chart )    CHART=$2; shift; shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done
 
echo LATEST=$LATEST
echo CHART=$CHART

调用上述脚本的示例:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
./push.sh  --latest --chart ngress gmem digital
# $1=--latest
# $2=--chart
# $3=ngress
# $4=gmem
# $5=digital
# LATEST=true
# CHART=ngress
 
./push.sh  --latest  gmem digital
# $1=--latest
# $2=gmem 注意非参数的位置改变
# $3=digital
# $4=
# $5=
# LATEST=true
# CHART=
变量的间接引用

例如:eval var1=\$$var2

限定类型的变量

declare 或者typeset 内建命令(这两个命令是完全一样的)允许指定变量的具体类型

 命令  说明
declare -r var1[=value1]  与readonly var1 是完全一样,强制指定只读
declare -i number  把变量"number"后边的赋值视为一个整型
declae -a indices  变量 indices 将被视为数组
declare -x var3  把var3 export 出来
引号内的字符串

引号可以保护字符串的字面值,避免特殊字符被重新解释,或者被Shell扩展

Shell
1
grep '[Ff]irst' *.txt #基于正则式搜索*.txt文件的内容
参数替换
 语法 说明 
${parameter}  与$parameter 相同,可以避免混淆
${parameter-default}  如果 parameter 没被set,那么就使用default。例如:echo ${username-`whoami`}
${parameter:-default}
${parameter=default}  如果 parameter 未设置,那么就设置为default
${parameter:=default}

${parameter+alt_value}

 如果 parameter 被set 了,那就使用alt_value,否则就使用null 字符串
${parameter:+alt_value}
${parameter?err_msg}  如果 parameter 被set,那就是用set 的值,否则打印err_msg
${parameter:?err_msg}
${#var}  字符串长度 
${#array} 数组中第一个元素的长度
${#array[*]}  数组元素的个数
${$#array[@]}
${array[@]} 将数组扩展为列表
${var#Pattern} 从$var 开头删除最近匹配$Pattern 的子串
${var##Pattern} 从$var 开头删除最远匹配$Pattern 的子串
${var%Pattern}  从$var 结尾删除最近匹配$Pattern 的子串
${var%%Pattern} 从$var 结尾删除最远匹配$Pattern 的子串
${var:pos}  变量 var 从位置pos 开始扩展
${var:pos:len}  从位置 pos 开始,并扩展len 长度个字符
${var/Pattern/Repl}  使用 Replacement 来替换var 中的第一个Pattern 的匹配
${var//Pattern/Repl}  全局替换。在var 中所有的匹配,都会用Replacement 来替换
${var/#Pattern/Repl}  如果 var 的前缀匹配到了Pattern,那么就用Replacement 来替换Pattern
${var/%Pattern/Repl}  如果 var 的后缀匹配到了Pattern,那么就用Replacement 来替换Pattern
${!varprefix*}  使用变量的前缀来匹配前边所有声明过的变量
${!varprefix@}
${parameter^^} 转换为大写
${parameter^}
${parameter,,} 转换为小写
${parameter,}
命令替换

处理一个或者多个命令的输出,并字面的(literally)将其赋予另外一个上下文(设置变量、作为另外一个命令的参数、甚至生成for循环的列表)。命令替换的典型的语法是反引用包围的命令。命令替换以subshell形式调用

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
script_name=`basename $0`
echo "The name of this script is $script_name."
 
textfile_listing=`ls *.txt`
#命令替换的可选语法:textfile_listing=$(ls *.txt)
echo $textfile_listing
 
#把文件内容赋值给变量
variable1=`<file1`
variable2=`cat file2`
#命令替换可能导致单词分隔
COMMAND `echo a b` #COMMAND有两个参数a、b
COMMAND "`echo a b`" #一个参数 "a b"
COMMAND `echo` #没有参数
COMMAND "`echo`" #一个空参数
捕获输出
标准输出
Shell
1
VAR=$(command)
标准错误
Shell
1
ERROR=$(cat file1.txt nofile.txt 2>&1 > /dev/null)
同时捕获 
Shell
1
VAR=$(cat file1.txt nofile.txt 2>&1) 
算术扩展

算数扩展提供了强大的整数算术操作机制。

Shell
1
2
3
4
5
z=`expr $z + 3`
z=$(($z+3))       #双括号,注意不要和命令替换混淆
z=$((z+3))        #双括号中,变量解引用可有可无
let z=z+3
let "z += 3"      #引号允许在变量赋值语句中使用空格 
字符串处理
字符串长度
Shell
1
2
3
4
5
#下面3种方式均可得到字符串长度
stringZ=abcABC123ABCabc
echo ${#stringZ} # 15
echo `expr length $stringZ` # 15
echo `expr "$stringZ" : '.*'` # 15
子串匹配
Shell
1
2
3
4
expr match "$string" '$substring'    #$substring 是一个正则表达式
expr "$string" : '$substring'        #$substring 是一个正则表达式
#举例
echo `expr match "$stringZ" 'abc[A-Z]*.2'`
前缀/后缀匹配
Shell
1
2
3
4
5
#                注意:不要加引号
                 # 前缀匹配              # 后缀匹配
if [[ "$line" == flannel* || "$line" == *$K8S_VERSION ]]; then
  echo $line
fi 
子串索引
Shell
1
2
3
4
expr index $string $substring    #匹配到子串的第一个字符的位置
#举例
stringZ=abcABC123ABCabc
echo `expr index "$stringZ" C12`   #6
提取子串
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#在string中从位置$position 开始提取子串
#如果$string 为"*"或"@",那么将提取从位置$position 开始的位置参数
${string:position}
 
#在string中从位置$position 开始提取$length 长度的子串
${string:position:length}
 
#举例
stringZ=abcABC123ABCabc
echo ${stringZ:0}     # abcABC123ABCabc
echo ${stringZ:1}     # bcABC123ABCabc
echo ${stringZ:7}     # 23ABCabc
echo ${stringZ:7:3}   # 23A
echo ${stringZ:(-4)}  # Cabc
echo ${*:2}           # Echo 出第2个和后边所有的位置参数
echo ${@:2}           # 与前边相同
echo ${*:2:3}         # 从第2个开始,Echo后边3个位置参数
if [ "${1:0:1}" = '-' ]; then echo " $1 is a option"; fi
 
#在string中从位置$position开始提取$length 长度的子串
expr substr $string $position $length
#举例
stringZ=abcABC123ABCabc
echo `expr substr $stringZ 1 2` # ab
echo `expr substr $stringZ 4 3` # ABC
 
#从$string 的开始位置提取$substring,$substring 是一个正则表达式
expr match "$string" '\($substring\)'
expr "$string" : '\($substring\)'
#举例
stringZ=abcABC123ABCabc
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
删除子串
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#从$string 的左边截掉第一个匹配的$substring
${string#substring}
#从$string 的左边截掉最后一个匹配的$substring
${string##substring}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ#a*C} # 123ABCabc   截掉'a'和'C'之间最近的匹配
echo ${stringZ##a*C} # abc        截掉'a'和'C'之间最远的匹配
 
#从$string 的右边截掉第一个匹配的$substring
${string%substring}
#从$string 的右边截掉最后一个匹配的$substring
${string%%substring}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ%b*c}  # abcABC123ABCa 从$stringZ 的后边开始截掉'b'和'c'之间的最近的匹配
替换子串
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#使用$replacement 来替换第一个匹配的$substring
${string/substring/replacement}
#使用$replacement 来替换所有匹配的$substring
${string//substring/replacement}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ/abc/xyz}  # xyzABC123ABCabc
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
 
#如果$substring 匹配$string 的开头部分,那么就用$replacement 来替换$substring
${string/#substring/replacement}
#如果$substring 匹配$string 的结尾部分,那么就用$replacement 来替换$substring
${string/%substring/replacement}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ
退出与退出状态

exit 命令被用来结束脚本,返回一个值来传给父进程,如果没有exit,则最后执行的命令的exit状态被返回。

每个命令都会返回一个 exit 状态(有时候也叫return 状态),成功返回0,如果返回一个非0 值,通常情况下都会被认为是一个错误码。

变量:$?用于读取退出码。

内部变量
 变量/函数名 说明 
 $BASH Bash 的二进制执行文件的位置
 $BASH_ENV 指向一个 Bash 启动文件,这个启动文件将在调用一个脚本时被读取
 $BASH_SUBSHELL 提示subshell 的层次
 $BASH_VERSINFO[n] 记录 Bash 安装信息的一个6 元素的数组
 $BASH_VERSION 安装在系统上的 Bash 的版本号
 $DIRSTACK 在目录栈中最上边的值(将受到pushd 和popd 的影响)
 $EDITOR 脚本调用的默认编辑器
 $EUID "effective"用户ID
 $FUNCNAME 当前函数的名字
 $GLOBIGNORE 文件名的模式匹配列表
 $GROUPS 当前用户属于的组
 $HOME 用户的 home 目录
 $HOSTNAME 主机名
 $HOSTTYPE 主机类型,识别系统的硬件,例如i686
 $IFS 内部域分隔符,这个变量用来决定 Bash 在解释字符串时如何识别域、单词边界。默认为空白(空格、TAB、换行)
 $IGNOREEOF 忽略 EOF
 $LINENO 记录它所在的 shell 脚本中它所在行的行
 $MACHTYPE 提示系统硬件
 $OLDPWD 老的工作目录
 $OSTYPE 操作系统类型
 $PATH 指向 Bash 外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等
 $PIPESTATUS 数组变量将保存最后一个运行的前台管道的退出码
 $PPID 父进程的进程
 $PROMPT_COMMAND 保存一个在主提示符($PS1)显示之前需要执行的命令
 $PS1 主提示符,具体见命令行上的显示
 $PS2 第 2 提示符,当需要额外的输入的时候将会显示,默认为">"
 $PS3 第 3 提示符,在select 循环中显示
 $PS4 第 4 提示符,当使用-x 选项调用脚本时,这个提示符将出现在每行的输出前边,默认为"+"
 $PWD 工作目录
 $REPLY read命令如果没有给变量,输入将保存在$REPLY 中
 $SECONDS 脚本已经运行的时间(单位为秒)
 $SHELLOPTS 保存 shell 允许的选项,这个变量是只读的
 $SHLVL Shell层次,就是shell 层叠的层次,如果是命令行那$SHLVL 就是1,递增
 $TMOUT 如果$TMOUT 环境变量被设置为一个非零的时间值,那么在过了这个指定的时间之后,shell提示符将会超时,这会引起一个logout
 $UID 用户 ID 号
 $0, $1, $2...

位置参数,从命令行传递给脚本,或者是传递给函数

$0 为脚本文件的名字
$1 为第一个参数,类推

 $# 命令行或者是位置参数的个数
 $*

所有的位置参数,被作为一个单词。注意:"$*"必须被""引用

 $@ 与$*同义,但是每个参数都是一个独立的""引用字串。注意:"$@"必须被""引用
 $! 在后台运行的最后的工作的 PID
 $_ 保存之前执行的命令的最后一个参数
 $? 命令,函数或者脚本本身的退出状态
 $$ 脚本自身的进程 ID
 $RANDOM  产生一个随机整数
条件测试与分支
简介

一个 if/then结构可以测试命令的返回值是否为零(0表示成),如果是的话,执行更多命令。

内建命令 [与 test命令等价。这个命令把它的参数作为比较表达式或是文件测试,并且根据比较的结果,返回一个退出码。

Bash 2.02以后,关键字 [[...]]用于扩展test命令。 例如 [[ $a -lt $b ]]是一个单独的元素,返回一个退出码。

((...))和 let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0 的时候,退出码将返回0。

[[]]结构比 []更加灵活,前者支持参数扩展和命令替换。

test/[命令
语法
Shell
1
2
3
4
5
6
7
8
9
test EXPRESSION
 
test
 
[ EXPRESSION ]
 
[ ]
 
[ OPTION
表达式

EXPRESSION的语法如下:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 空白表达式为假
 
 
# 如果表达式为真,通过测试(结果为真)
( EXPRESSION )
 
# 如果表达式为假,通过测试
! EXPRESSION
 
# 逻辑与
EXPRESSION1 -a EXPRESSION2
 
# 逻辑或
EXPRESSION1 -o EXPRESSION2
 
# 字符串长度不为零,通过测试
-n STRING
 
# 如果字符串长度为零,通过测试
-z STRING
 
# 字符串比较、文件测试、数字比较见下文
示例
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[ "$1" = 'mysqld' -a -n "$CLUSTER_INIT" ]   # 如果第一个参数为mysqld,并且变量CLUSTER_INIT不为空
 
(( 0 && 1 ))                                # 逻辑与,$?为1
let "num = (( 0 && 1 ))"                    # num的值为0,故$?为1
(( 200 || 11 ))                             # 逻辑或,$? # 0
let "num = (( 200 || 11 ))"                 # num的值为1,故$?为0
(( 200 | 11 ))                              # 按位或,$?为0    
  
# 注意,本行不会打印var变量,因为算术扩展的退出状态不是一个错误码                                  
var=-2 && (( var+=2 )) && echo $var    
 
# 测试任何命令    
if grep -q Bash file                        
    then echo "At least one Bash in file"
fi
# []或者[[]]测试时,必须与内部表达式之间保留一个空格
 
# 如果在同一行上边写多个子句,必须使用; 来分隔(注意后面的空格)
if [ ! -x "$filename" ]; then :               #空命令:不执行任何动作
else if [0]; then :
elif [1]; then :                            #elif是else if的简写
fi
 
# 测试上一个命令的退出码
if [[ $? -eq 0 ]] ; then echo 'Successful'; fi
if [[ $? -eq 0 ]] ; then
    echo 'Succeeded'
    echo 'OK'
else
    echo 'Failed'
fi
 
# 测试第四个参数是否存在
if [ -z "$4" ] ; then
   echo "Argument 4 not exist"
fi
 
# 所有内容集中在单行编写的例子
if [[ $? -eq 0 ]]; then echo FOUND; fi
# 使用逻辑与/或
if [[ 1 -eq 1 && 2 eq 2 ]]; then : fi
if [[ 1 -eq 1 ]] || [[ 2 -eq 1 ]]; then : fi
文件测试
测试符   说明
-e 文件存在
-f file 是一个普通文件,如果file是目录、设备文件,或者file不存在,则测试不通过
-s 文件长度不为0
-d 文件是个目录
-b 文件是个块设备(软盘,cdrom 等等)
-c 文件是个字符设备(键盘,modem,声卡等等)
-p 文件是个管道
-h 文件是个符号链接
-L 文件是个符号链接
-S 文件是个socket
-t 关联到一个终端设备的文件描述符
这个选项一般都用来检测是否在一个给定脚本中的 stdin[-t0]或[-t1]是一个终端
-r 文件具有读权限(对于用户运行这个test)
-w 文件具有写权限(对于用户运行这个test)
-x 文件具有执行权限(对于用户运行这个test)
-g 目录是否具有 sgid 标志
-u 文件是否具有suid标志。如果运行一个具有 root 权限的文件,那么运行进程将取得root 权限,即使你是一个普通用户,如果没有 suid 标志的话,那么普通用户(没有root 权限)将无法运行这种程序
-k

检查sticky bit,设置在文件上,将保存在交换区;设置在目录上,将限制写权限:如果用户不是该目录的所有者,但是具有写权限,那么他只能删除目录下自己拥有的文件。

设置了此标志位的文件或目录(例如/tmp),权限尾部有t标记,例如:drwxrwxrwt

-O 是否当前用户是文件所有者
-G 是否文件的group-id 与当前用户相同
-N 从文件最后被阅读到现在,是否被修改
f1 -nt f2 是否文件 f1 比f2 新
f1 -ot f2 是否文件 f1 比f2 老
f1 -ef f2 是否f1和f2 都硬连接到同一个文件
! 可以反转上述测试的结果
其他比较操作符
 操作符 说明 
 整数比较
 -eq 等于,如:if [ "$a" -eq "$b" ]
 -ne 不等于,如:if [ "$a" -ne "$b" ]
 -gt 大于,如:if [ "$a" -gt "$b" ]
 -ge 大于等于,如:if [ "$a" -ge "$b" ]
 -lt 小于,如:if [ "$a" -lt "$b" ]
 -le 小于等于,如:if [ "$a" -le "$b" ]
 < 小于(需要双括号),如:(("$a" < "$b"))
 <= 小于等于(需要双括号),如:(("$a" <= "$b"))
 > 大于(需要双括号),如:(("$a" > "$b"))
 >= 大于等于(需要双括号),如:(("$a" >= "$b"))
  字符串比较
 = 等于,如:if [ "$a" = "$b" ]
 ==

等于,如:if [ "$a" == "$b" ],与=等价,注意其行为在[] [[]]中的不同

[[ $a == z* ]] # 如果$a 以"z"开头(模式匹配)那么将为true
[[ $a == "z*" ]] # 如果$a 等于z*(字符匹配),那么结果为true
[ $a == z* ] # File globbing(一种关于文件的速记法,比如"*.c"、"~") 和word splitting 将会发生
[ "$a" == "z*" ] # 如果$a 等于z*(字符匹配),那么结果为true

 != 不等于,如:if [ "$a" != "$b" ]
 <

小于,按ASCII 字母顺序比较,例如

if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]  #在[]中,<需要转义

 > 大于,按ASCII 字母顺序比较
-z 测试字符串为"null",即长度为0
-n 测试字符串不为"null"
混合比较
-a 逻辑与
-o 逻辑或
嵌套的测试
Shell
1
2
3
4
5
6
7
if [ condition1 ]
then
    if [ condition2 ]
    then
    do-something # 这里只有在condition1 和condition2 都可用的时候才行.
    fi
fi
CASE分支结构
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case "$variable" in
"$condition1")         #测试行都以右小括号)结尾
    command...
;;                     #条件块都以两个分号结尾
"$condition1")
    command...
;;
esac
#举例
read Keypress
case "$Keypress" in
[[:lower:]] ) echo "Lowercase letter";;
[[:upper:]] ) echo "Uppercase letter";;
[0-9] )       echo "Digit";;
* )           echo "Punctuation, whitespace, or other";;
esac
循环结构
for循环
语法
Shell
1
2
3
4
5
#for循环
for arg in [list]
do
    command(s)...
done
分隔符

默认的分隔符是whitespace,也就是每当遇到分隔符,就会产生新的arg。要修改分隔符为换行,在循环前添加:

Shell
1
IFS=$'\n'
C风格for循环
Shell
1
2
3
4
5
6
7
8
LIMIT=10
for ((a=1, b=1; a <= LIMIT ; a++, b++))
do
    echo -n "$a "
done
 
# 无限循环
for (( ; ; ))
while循环
Shell
1
2
3
4
while [condition]
do
    command...
done
C风格while循环 
Shell
1
2
3
4
5
6
((a = 1))
while (( a <= LIMIT ))
do
    echo -n "$a "
    ((a += 1)) # let "a+=1"
done
until循环
Shell
1
2
3
4
until [condition-is-true]
do
    command...
done 
循环举例
无限循环
Shell
1
2
3
4
5
# 无限循环1
while :
 
# 无限循环2
while true
数字范围循环
Shell
1
2
3
4
5
6
7
8
9
10
11
# 遍历范围
var0=0
LIMIT=10
while [ "$var0" -lt "$LIMIT" ]
do
    echo -n "$var0 "
    var0=`expr $var0 + 1`
    # var0=$(($var0+1))
    # var0=$((var0 + 1))
    # let "var0 += 1"
done
遍历命令输出

可以使用管道:

Shell
1
2
3
4
5
6
ip route | while read -r route; do
  dest=$(echo $route | awk '{print $1}')
  if [[ "$dest" != *"/"* ]]; then
    continue
  fi
done

注意,某些命令结尾没有\n,这会导致遍历的时候最后一行被略过,需要这样:

Shell
1
2
3
4
5
6
7
8
{ yourcmd; echo; } | while read -r line; do echo $line; done
 
# 遍历文件的每一行,注意,echo确保文件不以换行符结尾的情况下,最后一行不丢失
{ cat images.txt; echo; } | while read -r line; do
  if [[ "$src" == "" ]]; then
    continue
  fi
done

使用管道时,while运行在subshell中,缺点是无法修改变量,可以使用Here String规避:

Shell
1
2
3
4
5
6
declare -A tgt_service_versions
while read -r line; do
  comp=$(echo "$line" | yq r - '.service_name')
  version=$(echo "$line" | yq r - '.tmplgrp_version')
  tgt_service_versions[$comp]=$version
done <<< "$(yq r global.yaml ".vars.services")"
遍历字符串列表
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 遍历字符串列表,注意,如果字符串加引号,作为整体看待
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
    echo $planet # Each planet on a separate line.
done
 
# 遍历字符串列表,使用变量
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind"
for file in $FILES
do
    if [ ! -e "$file" ]
        break
        continue 1   # 控制循环的行为,数字用于跳出或继续N层循环
    fi
done
遍历文件列表 
Shell
1
2
3
4
5
6
7
# 遍历文件列表。如果列表中包含通配符*?,则会扩展文件名,即file globbing
#   *所有文件
#   [jx]* 所有j或者x开头的文件
for file in *; do ...
for cfg in config/*; do
    if
done
遍历参数数组 
Shell
1
2
3
4
5
# 遍历参数数组。如果忽略列表,则操作$@
for a
do
    echo -n "$a "
done 
内建命令

一个内建命令通常与一个系统命令同名,但是Bash 在内部重新实现了这些命令

 命令 说明 
echo

打印(到stdout)一个表达式或变量,可以用来作为一系列命令的管道输入
-e 打印转义字符,如果不指定,斜杠转义按字面输出
-n 阻止新起一行
注意:echo默认会把换行符替换为空格,要改变此行为,需要在被打印变量外面加""

printf

格式化输出,是echo 命令的增强。与C语言同名函数类似

declare / typeset

declare [-aAfFgilnrtux] [-p] [name[=value] ...]

声明变量,可选的,提供属性

如果不指定任何name,则打印所有变量的值

-p 用于显示变量的值和属性,使用该选项,则其它额外的(除了-f/-F)选项被忽略

-a 声明变量为索引数组
-A 声明变量为关联数组
-i 变量被作为整数看待,变量被赋值时,自动进行算术扩展
-l 赋值的时候,自动转换为小写
-u 赋值的时候,自动转换为大写 
-r 声明为只读变量,无法被后续赋值或unset
-x 变量被export为环境变量

所有选项前面的 - 替换为+,则关闭对应选项

local

local [option] [name[=value] ...]

在函数内部使用,声明局部变量,option和declare命令相同

read

从 stdin 中读取一个变量的值
-a 取得数组变量
-s 不回显输入
-d 读取到该选项指定的字符,而不是默认的换行符
-n N 只接受N个字符的输入
-p 给出提示文字
-t  等待用户输入的时间
-r 禁止 \ 进行任何字符的转义

使用read 命令时,输入一个\然后回车,会阻止产生一个新行

cd -P 忽略符号连接
cd - 切换工作目录为$OLDPWD
pwd 打印当前的工作目录
pushd pushd dir-name 把路径dir-name 压入目录栈,同时修改当前目录到dir-name
popd popd 将目录栈中最上边的目录弹出,同时修改当前目录到弹出来的那个目录
dirs 列出所有目录栈的内容
let 执行变量的算术操作,可以看作expr的简化版
eval eval arg1 ... [argN]  将表达式中的参数、或者表达式列表组合起来并估算
set

修改内部脚本变量的值。或者以一个命令的结果(set `command`)来重新设置脚本的位置参数,例如:set `uname -a`; 
echo $_

 

也可以用来设置Bash的行为选项:
-e 如果一个管道、子Shell命令(括号中包围)、列表(花括号包围)中的某个命令以非0退出,则立即退出Shell。以下情况除外:

  1. 如果命令列表后面是while/until关键字
  2. 如果命令位于if/elif测试中
  3. 位于&&或||中,且不在尾部
  4. 位于管道(Pipeline)中,且不在尾部

你可以在Shell退出前捕获ERR信号

-f 禁用路径名(Pathname)展开

-m 监控模式,启用Job控制,后台进程运行在独立的进程组中

-o pipefail 如果管道中一个命令失败,则整个管道的退出码为非0

-o errexit  如果某个命令或管道失败,立即退出脚本,等同于 -e

+e、+o ... 取消之前的 -e、-o的效果

unset 来删除一个shell 变量(设置为null)。对位置参数无效
export 使得被export 的变量在运行的脚本(或shell)的所有的子进程中都可用。该命令重要的使用就是用在启动文件中设置环境变量
readonly 与 declare -r 作用相同
getopts 允许传递和连接多个选项到脚本中,并能分配多个参数到脚本中
source / .

在命令行上执行的时候,将会执行一个脚本

在一个文件内一个source file-name将会加载 file-name 文件;source 一个文件将会在脚本中引入代码,并附加到脚
本中(类似于#include)

exit 停止一个脚本的运行。不带参数的exit,等价于exit $?,即最后一条命令的退出码
exec

使用一个特定的命令来取代当前进程。一般的当shell遇到一个命令时,会fork off子进程来运行,使用exec 内建命令将会替换掉当前 shell,命令执行完毕即导致shell进程立即退出

也可以用来重定向脚本的标准输出

shopt 允许 shell 在空闲时修改shell 选项
caller 在stdout 上打印出函数调用者的信息
ture 一个返回成功(0)退出码的命令
flase 一个返回失败(非0)退出码的命令
type [command] 将给出command的完整路径
bind 令用来显示或修改readline的键绑定
shift

向左移动参数数组$@的元素,可以指定一个数字,表示移动的个数,如果不指定则移动一个

移动1后,$@长度减小1,$1变为¥0

作业控制命令(Job Control Commands)

作业标识符

%N  作业号[N]
%S  以字符串S 开头的被(命令行)调用的作业
%?S 包含字符串S 的被(命令行)调用的作业
%%、%+ 当前作业(前台最后结束的作业,或后台最后启动的作业)
%-  当前作业退出后,该作业变成当前作业
$!   最后的后台进程

jobs

在后台列出所有正在运行的作业,给出作业号。kill %作业号和kill 进程号等价:

Shell
1
2
3
4
5
6
7
8
# 方括号内的是作业号
# Running表示正在运行的作业, 它会打印到标准输出
[1]- Running ping www.baidu.com &
# +表示当前作业,如果fg、bg不指定参数,则针对当前作业
# Stopped表示挂起的作业,但是进程并没有退出
[2]+  Stopped                 ping www.163.com
# -表示如果当前作业退出,则它会变成当前作业
[3]-  Stopped                 ping www.qq.com
disown 从 shell 的当前作业表中,删除作业
fg

把一个后台作业放到前台来运行。不指定作业号,则对当前作业进行处理

bg

新启动一个挂起的作业,并且在后台运行它。不指定作业号,则对当前作业进行处理

wait 停止脚本的运行,直到后台运行的所有作业(或者指定作业号或进程号)都结束为止
suspend 类似Control-Z,但是它挂起的是当前shell
logout 退出一个登陆的 shell,可以指定一个退出码
times 给出执行命令所占的时间
kill 通过发送一个适当的结束信号,来强制结束一个进程
command

禁用别名和函数的查找,只查找内部命令以及搜索路径中找到的脚本或可执行程序

Bash查找优先级:1.别名 2.关键字 3.函数 4.内置命令 5.脚本或可执行程序($PATH)

builtin 在"builtin"后边的命令将只调用内建命令
enable 禁用内建(-n)命令或者恢复(-a)内建命令
autoload 带有"autoload"声明的函数,在它第一次被调用的时候才会被加载
外部、系统与管理命令

参见:Linux命令知识集锦

高级特性
Here Documents
所谓Here Documents,是一种特殊用途的代码块,使用某种形式的I/O重定向来为交互式程序或命令提供输入,其形式如下:
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
command --args << EOF
...
#注意在这里会发生变量替换,因此需要必要的转义
\$PWD is $PWD
...
...
EOF
 
# 将Here Document写入到文件
cat << EOF  > /path/to/file
drill.exec: {
  cluster-id: "$CLUSTER_ID",
  zk.connect: "$ZK_CONNECT"
}
EOF
禁止变量替换

下面两种语法都可以:

Shell
1
2
3
4
5
6
7
8
cat - <<'EOF'
$KNOWN
EOF
# 输出 $KNOWN
 
cat - <<\EOF
$KNOWN
EOF
Here String

可以看作是Here Document的stripped-down形式。字符串被展开,并逐个喂给命令的stdin:

Shell
1
COMMAND <<< $WORD

示例:

Shell
1
2
3
4
5
6
7
8
if grep -q "txt" <<< "$VAR"
then
   echo "$VAR contains the substring sequence \"txt\""
fi
 
String="This is a string of words."
# 将字符串中的每个字符写入为数组元素
read -r -a Words <<< "$String" 
I/O重定向

Linux中,有三个文件:stdin、stdout、stderr总是处于打开状态的。这些文件或者任何打开的文件,可以被重定向。所谓重定向,就是捕获来自文件、命令、程序、脚本甚至代码块的输出(output),并将其作为其它文件、命令、程序或者脚本的输入。

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#重定向输出到文件,如果不存在,则创建,否则覆盖
ls -lR > dir-tree.list
: > filename     #将文件的内容清空,如果不存在则创建
 
#重定向输出到文件,如果不存在,则创建,否则附加到文件结尾
ls -lR >> dir-tree.list
 
1>filename       #把标准输出重定向到文件
2>filename       #把标准错误重定向到文件
&>filename       #把标准输出、错误同时重定向到文件
 
LOGFILE=script.log
echo "命令的输出被重定向到文件描述符" 1>$LOGFILE
echo "命令的错误被重附加到文件描述符" 2>>$LOGFILE
 
2>&1             #把标准错误重定向到标准输出
i>&j             #重定向文件描述符i到文件描述符j
>&j              #重定向默认文件描述符(1,stdout)到j
 
0< FILENAME      #从文件读取输入
 
[j]<>filename    #打开文件进行读写,并分配文件描述符j
exec 3<> File    #打开File,并分配3位文件描述符
read -n 4 <&3    #从文件描述符3读入4字符
echo -n . >&3    #输出一个小数点到文件描述符3
exec 3>&-        #关闭文件描述符3
 
n<&-             #关闭输入文件描述符n
0<&-, <&-        #关闭标准输入
n>&-             #关闭输出文件描述符n
1>&-, >&-        #关闭标准输出
 
while [ "$name" != Smith ]
do
    read name
    echo $name
    let "count += 1"
done <"$Filename"#重定向代码块的输入
子Shell

Subshell指Shell发动的子进程。一般的,外部命令会发动子Shell,而内置命令则不会,此外,括号包含的命令列表发动Subshell:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
( command1; command2; command3; ... )
 
#Subshell可用于构建专用运行环境
COMMAND1
COMMAND2
COMMAND3
(
    IFS=:
    PATH=/bin
    unset TERMINFO
    set -C
    shift 5
    COMMAND4
    COMMAND5
    exit 3 #这里只会退出Subshell
)

 Subshell中的变量,在代码块以外无法访问,相当于局部变量。

受限Shell

在受限模式下运行的Shell,一些命令被禁用:

  1. 使用cd改变当前目录
  2. 修改环境变量$PATH, $SHELL, $BASH_ENV,  $ENV
  3. 读取或者改变$SHELLOPTS
  4. 重定向输出
  5. 调用包含/符号的命令
  6. 调用exec命令来替换Shell的进程
  7. 解除受限模式
  8. 其它一些命令

使用set -r、set --restricted开启脚本受限模式。

进程替换

进程替换把一些进程的输出作为其它进程的输入来使用。命令格式如下(使用括号包围),注意没有空格:

>(command_list)
<(command_list)

函数

函数语法:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function function_name {
    command...
}
#第二种语法支持C-Style
function_name () {
    if [ -z "$1" ] #$1表示第一个参数
    then
        echo "-Parameter #1 is zero length.-"
    else
        echo "-Param #1 is \"$1\".-"
    fi
}
#调用函数
function_name
function_name $arg1 $arg2  #传参,只支持传值方式,可以使用间接引用来传引用
#支持函数嵌套
f1 ()
{
    f2 () # nested
    {
        echo "Function \"f2\", inside \"f1\"."
    }
}

 函数的退出:函数返回一个值,称为exit status,这与命令退出码类似。可以使用return语句指定返回值,否则,最后一条命令的退出码作为返回值。

别名

Bash别名可以简单的看作一个键盘快捷方式:

Shell
1
alias ll="ls -l"
列表构造

and list:如果前一个命令的退出码为0,则继续执行后面的命令

Shell
1
command-1 && command-2 && command-3 && ... command-n

 or list:如果前一个命令退出码味非0,则继续执行后面的命令

Shell
1
command-1 || command-2 || command-3 || ... command-n
数组

较新版本的Bash支持一维数组,数组元素以 variable[xx]的方式进行访问,通过 ${element[xx]}来获取元素的值。

Shell
1
2
3
4
5
6
7
8
9
10
area[11]=23
area[13]=37
area[51]=UFOs
 
echo ${area[11]}
area[5]=`expr ${area[11]} + ${area[13]}`
 
area2=( zero one two three four )
area3=([17]=seventeen [24]=twenty-four)
base64_charset=( {A..Z} {a..z} {0..9} + / = )
数组长度

没有下标的索引点,不会占用元素个数:

Shell
1
2
3
adobe=([0]='Flash' [2]='Flex' [4]='Photoshop')
echo ${#adobe[@]}
# 打印 3 
扩展为列表

可以使用表达式: ${array[@]}将数组转换为列表:

Shell
1
2
3
4
5
# 声明一个数组,它的元素来自DOCKER数组的所有成员 +
#                                    run字面之 +
#                                         docker_run_opts数组的所有成员 +
#                                                                KUBE_BUILD_IMAGE 变量
local -ra docker_cmd=("${DOCKER[@]}" run "${docker_run_opts[@]}" "${KUBE_BUILD_IMAGE}")
连接操作

通过扩展操作,可以连接两个数组:

Shell
1
2
3
adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere')
adobe2=('Fireworks' 'Illustrator')
adobe3=(${adobe[@]} ${adobe2[@]}) 
切片操作
Shell
1
2
3
4
5
adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere')
echo ${adobe[@]:1:3}
# 打印 Flex Photoshop Dreamweaver
echo ${adobe[@]:3}
# 打印 Dreamweaver Premiere
文件读取为数组
Shell
1
names=(`cat 'names.txt'`)
遍历数组
Shell
1
2
3
4
5
6
7
8
9
10
adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere')
 
for item in ${adobe[@]};do
    echo $item
done
 
len=${#adobe[@]}
for ((i=0;i<$len;i++));do
    echo ${adobe[$i]}
done 
关联数组

关联数组也就是字典/映射,声明语法:

Shell
1
2
3
4
declare -A archMap=(
  [x86_64]=amd64
  [aarch64]=arm64
)
数组长度
Shell
1
2
${#filetypes[*]}
${#filetypes[@]}
写入元素
Shell
1
archMap[arm64]=aarch64 
获取元素
Shell
1
2
arch=x86_64
${archMap[$arch]}
获取键列表
Shell
1
2
3
4
# 使用*,并且整个表达式用双引号包围,则结果是一个字符串
${!filetypes[*]}
# 使用@,并且整个表达式用双引号包围,则结果是一个数组
${!filetypes[@]}
获取值列表 
Shell
1
2
3
4
# 使用*,并且整个表达式用双引号包围,则结果是一个字符串
"${filetypes[*]}"
# 使用@,并且整个表达式用双引号包围,则结果是一个数组
${filetypes[@]}
遍历数组
Shell
1
2
3
for key in "${!filetypes[@]}"; do
  echo ${filetypes[$key]}
done
/dev和/proc

Unix、Linux的文件系统中,通常都有这两个特殊用途的子目录。

/dev

包含设备文件(device files),包括环回文件(loopback devices,例如/dev/loop0。所谓环回文件是一种虚拟机制,可以将一个普通文件作为块设备访问)。此目录下还包含若干常用的伪设备:/dev/null, /dev/zero,/dev/urandom, /dev/sda1, /dev/udp,/dev/tcp。

/dev/null:相当于一个黑洞,可以用于忽略输出:

Shell
1
2
3
4
5
6
7
#忽略命令的输出
cat $filename >/dev/null
#忽略命令的错误
rm $badname 2>/dev/null
 
#从中无法读取到任何东西,可以用于清空文件内容
cat /dev/null > /var/log/messages

 /dev/zero:该设备制造二进制0,主要用于生成交换文件

当在/dev/tcp/$host/$port这样的伪设备上执行命令时,Bash会打开一个TCP连接:

Shell
1
2
3
4
#从time.nist.gov获取时间
cat /dev/tcp/www.net.cn/80
echo -e "GET / HTTP/1.0\n" >&5
cat <&5
/proc

该目录中的文件是当前系统中正在运行的系统和内核进程的镜像,包含了运行时和统计信息。可以获取CPU、内存、电池等多种组件的信息。

信号处理

关于Linux信号相关的知识,参考Linux信号、进程和会话

注意:如果存在正在运行的子进程,Bash默认会忽略发送给父进程的信号。

trap命令

当你在终端窗口按下Ctrl + C或者Break键后,正常情况下应用程序会立即停止。但是你可以在脚本中指定信号处理函数。其格式为:

Shell
1
2
# arg默认为 - 表示捕获到信号时,需要执行的命令
trap [-lp] [[arg] sigspec ...]

示例用法:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 列出信号名称和编号的对应关系
trap -l
 
# 接收到HUP INT PIPE QUIT TERM这几种信号时,执行exit 1命令
trap "exit 1" HUP INT PIPE QUIT TERM
 
# 也可以指定函数来处理信号
sighdl() {
}
trap sighdl SIGTERM
 
# 重置信号的默认处理逻辑
trap 1 2
 
# 忽略信号
trap '' 1
trap行为

当进程收到信号后,在同一线程中发生以下事件序列:

  1. 当前进程正在进行的内置命令(不产生子进程)立即退出
  2. 程序转到trap指定的信号处理脚本执行
  3. 执行完毕后,到被中断的命令的下一行执行

例如下面的脚本:

Shell
1
2
trap "echo TRAPED" HUP INT PIPE QUIT TERM
while true; do :; done

当按下Ctrl + C后,信号处理脚本运行,但是然后会继续循环,程序无法停止。下面的代码则可以正常工作:

Shell
1
2
3
4
5
6
7
sighdl() {
    echo TRAPED
    TERM_FLAG=1
}
trap sighdl HUP INT PIPE QUIT TERM
 
while [ "$TERM_FLAG" != "1" ] ; do :; done 
处理ERR信号

设置选项 set -o errexit /  set -e后,任何一个命令出错,都导致脚本退出,并且释放ERR信号。

要捕获这个信号,你需要选项 set -o errexit -o errtrace / set -eE,并且:

Shell
1
2
# 出错时清理后台进程
trap 'kill $(jobs -p)' ERR
常见问题
字符串处理
分隔字符串
Shell
1
2
3
4
5
6
7
# 分割字符串 1,2,3,4,5,6,7,8,9,10
# IFS指定基于什么符号拆分,这里是逗号
IFS=',' read -ra ARRAY <<< `seq -s ',' 10`
for i in "${ARRAY[@]}"; do
    # 遍历分割后的子串
    echo $i
done
字符串匹配

可以使用Glob: if [[ $line = *"Server startup in"* ]]; then ...

零散问题
如何递归遍历目录
Shell
1
2
3
4
5
#!/bin/bash
shopt -s globstar
for file in ./*.go ./**/*.go ; do
    echo $file
done

./*表示当前目录下的所有文件, ./**/*则表示任意级别子目录下的所有文件。 需要Bash 4.0+。

在脚本内部重定向标准输出
Shell
1
2
3
exec 2> /var/log/on-startup.log   # 将当前脚本的标准错误重定向到文件
exec 1>&2                         # 将当前脚本的标准输出重定向到标准错误
set -x  
打印到标准错误
Shell
1
echo >&2 "ERROR: "
循环过早退出

如果你读取标准输入,并且循环处理:

Shell
1
2
3
while read p; do
  cmd $p
done < input.file

当循环体中的命令cmd也会读取标准输入时,上述循环会立即退出。

解决办法:

Shell
1
2
3
4
for p in `cat input.file`
do
  cmd $p
done
sudo echo >报错

报错信息:Permission denied

解决办法: sudo sh -c "echo '' > $LOG"

退出命令管道

如果在管道的某个命令中需要退出处理,你需要显式的关闭管道中的其它命令(Subshell),简单的例子:

Shell
1
2
3
4
5
6
7
8
9
10
tail -f /usr/local/$TOMCAT_PATH/logs/catalina.out | while read line
do
    if [[ $line = *"Server startup in"*  ]]; then
          # 找到管道前面的命令tail,并杀死
          TAIL_PID=`ps --ppid $PID  | grep tail | awk '{print $1}'`
          echo "Killing subprocess $TAIL_PID"
          kill -9 $TAIL_PID
          exit 0
    fi
done

如果管道由很多命令组成,可以将它们放置到同一进程组中,然后向进程组发送信号,一起终结它们:

Shell
1
2
3
4
5
6
7
8
9
10
11
# 项目的命令启用Job控制,导致后台进程运行在独立的进程组
set -m
 
tail -Fn0 /tmp/report | while :
do
    echo "pre"
    # 向后台进程组发送信号,使其中的所有进程终结
    sh -c 'PGID=$( ps -o pgid= $$ | tr -d \  ); kill -TERM -$PGID'
    echo "past"
done &  # 在后台运行
wait    # 等待上述后台任务退出 
得到脚本所在的目录
Shell
1
2
3
pushd `dirname $0` > /dev/null
SCRIPT_DIR=`pwd`
popd > /dev/null
转义单引号

单引号字符串默认是禁止转义的,如果其中包含成对的单引号本身,会自动被忽略。

Shell
1
echo '''a'''    # a

 如果其中包含的单引号不成对,属于语法错误。如果要在单引号字符串中使用单引号本身,可以:

Shell
1
echo  $'\'a\''

即,在字符串前面加上特殊符号$,即可启用ANSI C风格的转义

变量跨行问题
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#跨行编写的多行字符串变量
mls="
line 1
line 2
line 3
"
 
#echo的默认行为是把换行符修改为空格输出
echo $mls
#输出:line 1 line 2 line 3
 
#目标变量外面加上引号,则可避免此默认行为
echo "$mls"
#输出:
#
#line 1
#line 2
#line 3
#
#可以看到,首尾有空白行,这分别是第一个"后的空白行,以及line3后面的空白行
 
#行尾加上反斜杠,可以把相邻的两行作为单行看待:
mls="\
line 1
line 2
line 3\
"
#再执行echo "$mls",就不会有空白行了:
#line 1
#line 2
#line 3
 
mls="\
line 1
line 2\n
line 3\
"
#默认的,反斜杠转义是不被echo命令启用的,除非指定-e
#执行echo -e  "$mls",会发现\n被转义为换行输出:
#line 1
#line 2
 
#line 3
 
#可以使用 $'\n' 把新行添加到变量尾部
mls="$mls"$'\n'"line 4"
 
#使用Here Document捕获多行文本到变量
read -d '' mls << EOF
line 1git-study-note
line 2
line 3
EOF
#首尾不会出现空行
echo "$mls"
← 解释器模式
log4j配置文件样本 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • SVN知识集锦
  • Linux命令知识集锦
  • Windows命令知识集锦
  • 使用Python进行文本处理
  • Go语言IO编程

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2