Bash学习笔记
在Linux系统中,作为/bin/sh的标准Shell是GNU工具集中的bash(GNU Bourne-Again Shell),大多数Linux发行版中/bin/sh是指向/bin/bash的一个链接。
Bash编程中,可以通过PID文件进行简单的单例控制——仅仅同时仅仅允许脚本的单个实例在运行:
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 |
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; |
注意:显示方式、前景色、背景色都是可选的,而且顺序可以颠倒。示例:
1 2 3 4 |
# 恢复系统默认设置 \033[0m # 设置蓝色背景、白色前景、光标闪烁 \033[44;37;5m |
在脚本中使用色彩控制的例子:
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字符 |
字符 | 说明 | ||
# |
单行注释 |
||
; |
命令分隔符,可以在单行放置多个命令:
|
||
;; |
CASE子句终止符:
|
||
;;&、;& | Bash4+的CASE子句终止符 | ||
. | 点号命令,相当于source命令 | ||
. | 作为文件名的组成部分:隐藏文件的前缀 | ||
. | 正则式字符串匹配,匹配任何单字符 | ||
" | 部分引用,保留字符串中的大部分字符(继续重新解释:$、\、`),用于括起字符串,如果其中有特殊字符 | ||
' | 完全引用,保留字符串中的全部字符(无法表示'字符) | ||
, |
串联多个表达式,最后一个作为返回值:
|
||
, ,, | 参数替换时的小写转换,双字符表示所有字符都转换,单字符表示只转换第一个字符(逗号) | ||
\ |
转义,后面的字符按字面解析(Shell不做特殊解释)。 \n 意味着新的一行 |
||
\ |
后面跟命令,例如\cp,可以临时禁用alias |
||
/ | 文件路径分隔符 | ||
` | 命令替换,将命令的输出赋值为变量,在子shell中执行 | ||
: |
空命令,什么都不做,exit status=0(true)
|
||
! | 反转测试或者exit status的值 | ||
* | 文件名通配符 | ||
* | 正则式中,表示任意数量的字符 | ||
* | 乘号 | ||
** |
幂 |
||
** | 扩展文件匹配 | ||
? | 参数替换中,用于测试变量是否被设置 | ||
? | 通配符,匹配单个字符 | ||
$ | 变量替换:表示变量的内容,例如 var1=5 echo $var1 | ||
$ | 在正则式中表示行的结尾 | ||
${} | 参数替换 | ||
$' ... ' | 引用字符串扩展 | ||
$* $@ | 位置参数 | ||
$? | 退出状态码,包含一个命令、函数、脚本的返回值 | ||
$$ | 持有进程的标识符 | ||
() | 命令组。在子shell中执行,父shell无法见到其中的变量 | ||
() | 数组初始化:Array=(element1 element2 element3) | ||
{xx,yy,zz} |
花括号扩展:注意花括号中不能出现空格,除非\转义,或者引号包含之
|
||
{a..z} |
扩展的花括号扩展:
|
||
{} |
代码块,亦称内联组(inline group),创建一个匿名函数,但是其内部变量对外部可见。不会发起子Shell
|
||
[] | 测试其中的表达式 | ||
[[ ]] | 测试其中的表达式,比[]更灵活 | ||
[ ] | 数组元素:Array[1]=slot_1 echo ${Array[1]} | ||
[ ] | 在正则式中表示一个范围内的字符 | ||
$[ ... ] |
(废弃)整数扩展,估算其中的整数值:
|
||
(( )) |
算术扩展,和let 命令很像,允许算术运算和赋值。特别的,在某些场景下,允许使用C风格的语法,举例:
|
||
重定向符 |
command < filename:命令的标准输入来自文件 |
||
进程替换 | (command)> <(command) |
||
<, > |
ASCII比较
|
||
\<, \> | 正则式中表示单词边界 | ||
| |
管道,把前面命令的stdout作为后一个命令或者shell的stdin
管道作为子进程运行,因此不能修改变量 |
||
|| | 逻辑或,在测试中,导致返回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
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可以左移参数
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 |
调用上述脚本的示例:
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扩展
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形式调用
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`" #一个空参数 |
1 |
VAR=$(command) |
1 |
ERROR=$(cat file1.txt nofile.txt 2>&1 > /dev/null) |
1 |
VAR=$(cat file1.txt nofile.txt 2>&1) |
算数扩展提供了强大的整数算术操作机制。
1 2 3 4 5 |
z=`expr $z + 3` z=$(($z+3)) #双括号,注意不要和命令替换混淆 z=$((z+3)) #双括号中,变量解引用可有可无 let z=z+3 let "z += 3" #引号允许在变量赋值语句中使用空格 |
1 2 3 4 5 |
#下面3种方式均可得到字符串长度 stringZ=abcABC123ABCabc echo ${#stringZ} # 15 echo `expr length $stringZ` # 15 echo `expr "$stringZ" : '.*'` # 15 |
1 2 3 4 |
expr match "$string" '$substring' #$substring 是一个正则表达式 expr "$string" : '$substring' #$substring 是一个正则表达式 #举例 echo `expr match "$stringZ" 'abc[A-Z]*.2'` |
1 2 3 4 5 |
# 注意:不要加引号 # 前缀匹配 # 后缀匹配 if [[ "$line" == flannel* || "$line" == *$K8S_VERSION ]]; then echo $line fi |
1 2 3 4 |
expr index $string $substring #匹配到子串的第一个字符的位置 #举例 stringZ=abcABC123ABCabc echo `expr index "$stringZ" C12` #6 |
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 |
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'之间的最近的匹配 |
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 为脚本文件的名字 |
$# | 命令行或者是位置参数的个数 |
$* |
所有的位置参数,被作为一个单词。注意:"$*"必须被""引用 |
$@ | 与$*同义,但是每个参数都是一个独立的""引用字串。注意:"$@"必须被""引用 |
$! | 在后台运行的最后的工作的 PID |
$_ | 保存之前执行的命令的最后一个参数 |
$? | 命令,函数或者脚本本身的退出状态 |
$$ | 脚本自身的进程 ID |
$RANDOM | 产生一个随机整数 |
一个 if/then结构可以测试命令的返回值是否为零(0表示成),如果是的话,执行更多命令。
内建命令 [与 test命令等价。这个命令把它的参数作为比较表达式或是文件测试,并且根据比较的结果,返回一个退出码。
Bash 2.02以后,关键字 [[...]]用于扩展test命令。 例如 [[ $a -lt $b ]]是一个单独的元素,返回一个退出码。
((...))和 let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0 的时候,退出码将返回0。
[[]]结构比 []更加灵活,前者支持参数扩展和命令替换。
1 2 3 4 5 6 7 8 9 |
test EXPRESSION test [ EXPRESSION ] [ ] [ OPTION |
EXPRESSION的语法如下:
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 # 字符串比较、文件测试、数字比较见下文 |
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 |
!= | 不等于,如:if [ "$a" != "$b" ] |
< |
小于,按ASCII 字母顺序比较,例如 if [[ "$a" < "$b" ]] |
> | 大于,按ASCII 字母顺序比较 |
-z | 测试字符串为"null",即长度为0 |
-n | 测试字符串不为"null" |
混合比较 | |
-a | 逻辑与 |
-o | 逻辑或 |
1 2 3 4 5 6 7 |
if [ condition1 ] then if [ condition2 ] then do-something # 这里只有在condition1 和condition2 都可用的时候才行. fi fi |
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 |
1 2 3 4 5 |
#for循环 for arg in [list] do command(s)... done |
默认的分隔符是whitespace,也就是每当遇到分隔符,就会产生新的arg。要修改分隔符为换行,在循环前添加:
1 |
IFS=$'\n' |
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 (( ; ; )) |
1 2 3 4 |
while [condition] do command... done |
1 2 3 4 5 6 |
((a = 1)) while (( a <= LIMIT )) do echo -n "$a " ((a += 1)) # let "a+=1" done |
1 2 3 4 |
until [condition-is-true] do command... done |
1 2 3 4 5 |
# 无限循环1 while : # 无限循环2 while true |
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 |
可以使用管道:
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,这会导致遍历的时候最后一行被略过,需要这样:
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规避:
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")" |
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 |
1 2 3 4 5 6 7 |
# 遍历文件列表。如果列表中包含通配符*?,则会扩展文件名,即file globbing # *所有文件 # [jx]* 所有j或者x开头的文件 for file in *; do ... for cfg in config/*; do if done |
1 2 3 4 5 |
# 遍历参数数组。如果忽略列表,则操作$@ for a do echo -n "$a " done |
一个内建命令通常与一个系统命令同名,但是Bash 在内部重新实现了这些命令
命令 | 说明 | ||
echo |
打印(到stdout)一个表达式或变量,可以用来作为一系列命令的管道输入 |
||
printf |
格式化输出,是echo 命令的增强。与C语言同名函数类似 |
||
declare / typeset |
declare [-aAfFgilnrtux] [-p] [name[=value] ...] 声明变量,可选的,提供属性 如果不指定任何name,则打印所有变量的值 -p 用于显示变量的值和属性,使用该选项,则其它额外的(除了-f/-F)选项被忽略 -a 声明变量为索引数组 所有选项前面的 - 替换为+,则关闭对应选项 |
||
local |
local [option] [name[=value] ...] 在函数内部使用,声明局部变量,option和declare命令相同 |
||
read |
从 stdin 中读取一个变量的值 使用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`;
也可以用来设置Bash的行为选项:
你可以在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 一个文件将会在脚本中引入代码,并附加到脚 |
||
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] |
|||
jobs |
在后台列出所有正在运行的作业,给出作业号。kill %作业号和kill 进程号等价:
|
||
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命令知识集锦
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 |
下面两种语法都可以:
1 2 3 4 5 6 7 8 |
cat - <<'EOF' $KNOWN EOF # 输出 $KNOWN cat - <<\EOF $KNOWN EOF |
可以看作是Here Document的stripped-down形式。字符串被展开,并逐个喂给命令的stdin:
1 |
COMMAND <<< $WORD |
示例:
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" |
Linux中,有三个文件:stdin、stdout、stderr总是处于打开状态的。这些文件或者任何打开的文件,可以被重定向。所谓重定向,就是捕获来自文件、命令、程序、脚本甚至代码块的输出(output),并将其作为其它文件、命令、程序或者脚本的输入。
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"#重定向代码块的输入 |
Subshell指Shell发动的子进程。一般的,外部命令会发动子Shell,而内置命令则不会,此外,括号包含的命令列表发动Subshell:
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,一些命令被禁用:
- 使用cd改变当前目录
- 修改环境变量$PATH, $SHELL, $BASH_ENV, $ENV
- 读取或者改变$SHELLOPTS
- 重定向输出
- 调用包含/符号的命令
- 调用exec命令来替换Shell的进程
- 解除受限模式
- 其它一些命令
使用set -r、set --restricted开启脚本受限模式。
进程替换把一些进程的输出作为其它进程的输入来使用。命令格式如下(使用括号包围),注意没有空格:
>(command_list)
<(command_list)
函数语法:
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别名可以简单的看作一个键盘快捷方式:
1 |
alias ll="ls -l" |
and list:如果前一个命令的退出码为0,则继续执行后面的命令
1 |
command-1 && command-2 && command-3 && ... command-n |
or list:如果前一个命令退出码味非0,则继续执行后面的命令
1 |
command-1 || command-2 || command-3 || ... command-n |
较新版本的Bash支持一维数组,数组元素以 variable[xx]的方式进行访问,通过 ${element[xx]}来获取元素的值。
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} + / = ) |
没有下标的索引点,不会占用元素个数:
1 2 3 |
adobe=([0]='Flash' [2]='Flex' [4]='Photoshop') echo ${#adobe[@]} # 打印 3 |
可以使用表达式: ${array[@]}将数组转换为列表:
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}") |
通过扩展操作,可以连接两个数组:
1 2 3 |
adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere') adobe2=('Fireworks' 'Illustrator') adobe3=(${adobe[@]} ${adobe2[@]}) |
1 2 3 4 5 |
adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere') echo ${adobe[@]:1:3} # 打印 Flex Photoshop Dreamweaver echo ${adobe[@]:3} # 打印 Dreamweaver Premiere |
1 |
names=(`cat 'names.txt'`) |
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 |
关联数组也就是字典/映射,声明语法:
1 2 3 4 |
declare -A archMap=( [x86_64]=amd64 [aarch64]=arm64 ) |
1 2 |
${#filetypes[*]} ${#filetypes[@]} |
1 |
archMap[arm64]=aarch64 |
1 2 |
arch=x86_64 ${archMap[$arch]} |
1 2 3 4 |
# 使用*,并且整个表达式用双引号包围,则结果是一个字符串 ${!filetypes[*]} # 使用@,并且整个表达式用双引号包围,则结果是一个数组 ${!filetypes[@]} |
1 2 3 4 |
# 使用*,并且整个表达式用双引号包围,则结果是一个字符串 "${filetypes[*]}" # 使用@,并且整个表达式用双引号包围,则结果是一个数组 ${filetypes[@]} |
1 2 3 |
for key in "${!filetypes[@]}"; do echo ${filetypes[$key]} done |
Unix、Linux的文件系统中,通常都有这两个特殊用途的子目录。
包含设备文件(device files),包括环回文件(loopback devices,例如/dev/loop0。所谓环回文件是一种虚拟机制,可以将一个普通文件作为块设备访问)。此目录下还包含若干常用的伪设备:/dev/null, /dev/zero,/dev/urandom, /dev/sda1, /dev/udp,/dev/tcp。
/dev/null:相当于一个黑洞,可以用于忽略输出:
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连接:
1 2 3 4 |
#从time.nist.gov获取时间 cat /dev/tcp/www.net.cn/80 echo -e "GET / HTTP/1.0\n" >&5 cat <&5 |
该目录中的文件是当前系统中正在运行的系统和内核进程的镜像,包含了运行时和统计信息。可以获取CPU、内存、电池等多种组件的信息。
关于Linux信号相关的知识,参考Linux信号、进程和会话
注意:如果存在正在运行的子进程,Bash默认会忽略发送给父进程的信号。
当你在终端窗口按下Ctrl + C或者Break键后,正常情况下应用程序会立即停止。但是你可以在脚本中指定信号处理函数。其格式为:
1 2 |
# arg默认为 - 表示捕获到信号时,需要执行的命令 trap [-lp] [[arg] sigspec ...] |
示例用法:
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 "echo TRAPED" HUP INT PIPE QUIT TERM while true; do :; done |
当按下Ctrl + C后,信号处理脚本运行,但是然后会继续循环,程序无法停止。下面的代码则可以正常工作:
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 |
设置选项 set -o errexit / set -e后,任何一个命令出错,都导致脚本退出,并且释放ERR信号。
要捕获这个信号,你需要选项 set -o errexit -o errtrace / set -eE,并且:
1 2 |
# 出错时清理后台进程 trap 'kill $(jobs -p)' ERR |
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 ...
1 2 3 4 5 |
#!/bin/bash shopt -s globstar for file in ./*.go ./**/*.go ; do echo $file done |
./*表示当前目录下的所有文件, ./**/*则表示任意级别子目录下的所有文件。 需要Bash 4.0+。
1 2 3 |
exec 2> /var/log/on-startup.log # 将当前脚本的标准错误重定向到文件 exec 1>&2 # 将当前脚本的标准输出重定向到标准错误 set -x |
1 |
echo >&2 "ERROR: " |
如果你读取标准输入,并且循环处理:
1 2 3 |
while read p; do cmd $p done < input.file |
当循环体中的命令cmd也会读取标准输入时,上述循环会立即退出。
解决办法:
1 2 3 4 |
for p in `cat input.file` do cmd $p done |
报错信息:Permission denied
解决办法: sudo sh -c "echo '' > $LOG"
如果在管道的某个命令中需要退出处理,你需要显式的关闭管道中的其它命令(Subshell),简单的例子:
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 |
如果管道由很多命令组成,可以将它们放置到同一进程组中,然后向进程组发送信号,一起终结它们:
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 # 等待上述后台任务退出 |
1 2 3 |
pushd `dirname $0` > /dev/null SCRIPT_DIR=`pwd` popd > /dev/null |
单引号字符串默认是禁止转义的,如果其中包含成对的单引号本身,会自动被忽略。
1 |
echo '''a''' # a |
如果其中包含的单引号不成对,属于语法错误。如果要在单引号字符串中使用单引号本身,可以:
1 |
echo $'\'a\'' |
即,在字符串前面加上特殊符号$,即可启用ANSI C风格的转义
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" |
Leave a Reply