记录下遇到的有趣的shell代码, 可能是一些常用的snippet, 也可能是使用的时候不经意踩到的坑
不定时更新
正确传递数组到函数中 1 2 3 4 5 6 7 function update () { declare -a apps_version=("${!1} " ) echo "${apps_version[@]} " } APPS_VERSION=("aaa" "bbb" "ccc" ) update APPS_VERSION[@]
要特别注意的是,使用不当就造成只将数组的第一个数组到函数中
上面是正确的使用方法
使用sed修改Yaml文件中指定关键字的下N行 文件如下:
1 2 3 4 5 6 this: is: is a: a test: test is: is test: test
现在要修改this所在行的下面2行
1 sed -i "/this:/!b;n;n;c\ a: ${tmp_version} " ${file}
!b表示中断sed命令
n表示读入下一行
c表示将当前行修改为后面的字符串
因为a: a这行需要缩进2个空格,空格需要使用\
进行转义
查看进程占用的文件句柄 1 2 3 4 5 6 7 8 9 10 11 12 13 find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -print 2>/dev/null | cut -d/ -f3 |xargs -I '{}' -- ps --no-headers -o '%U' -p '{}' | sort | uniq -c | sort -nr find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -print 2>/dev/null | cut -d/ -f3 |xargs -I '{}' -- ps --no-headers -o '%U %p %c' -p '{}' | sort | uniq -c | sort -nr
使用sed删除多行内容 1 2 3 4 5 6 7 8 9 10 11 12 13 env: prod sensebee3: xxx: yyy: yyy svc_name: ss_class.ingress_namespace.svc.cluster.local sensebee: sensebee svc_port: 8443 node_port: 30123 http: 1234 sensebee2: http: 1234 svc_name: sensebee.default
对于上面的的yaml文件内容,如果想将sensebee3下的行直到http: 1234之间的内容都删除,但是sensebee3及http: 1234
这两行不删除,使用sed
如何操作呢?
1 sed -i "/^sensebee3:/I,/^[^[:space:]#]/{//!d;}" file
从上面的格式可以看出,只需要先确定范围,然后删除即可,
/^sensebee3:/
用于匹配sensebee3
/^[^[:space:]#]/
用于匹配到不是以空格及#
号开头的行,自然就匹配到了http: 1234
, 这样就选定了这两行之间的内容
{//!d;}
其中//
表示使用前面的正则表达式, !d
表示不删除, 这样就实现了sensebee3及http: 1234
这两行不会删除,只删除这两行之间的内容.
在脚本中修改crontab 1 (crontab -l 2>/dev/null; echo '*/2 * * * * bash /usr/local/src/kestrel.openfiles.check > /usr/local/src/logs/kestrel_openfiles.check.$(date "+\%Y\%m\%d-\%H\%M\%S").details 2>&1' ) | crontab -
善用{}
1 2 3 4 5 ATEST="ISTEST" # 错误使用,打印为空, bash会把ATEST_exec当成是一个变量,也就是最大的查找_连接的字符 echo $ATEST_exec # 正确 echo ${ATEST}_exec
使用&&
||
有时为了shell命令能够简短, 经常会连着使用 &&(且) ||(或)
, 但是如果不多想一次的话,可能就会跟结果相背.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 只有当command1成功(命令返回值为0), 才会执行command2 command1 && command 2 # 只有当command1失败(命令返回值不为0), 才会执行command2 command1 || command2 # command1与command2会顺序执行, 两个命令之间没有关系. command1; command2 # 例子 # 假如想判断从网上下载一个东西, 如果成功继续执行, 如果失败, 则打印一句话并退出. # 思考下面三个句子,哪条符合要求 # 这句话相当于(wget -q someURL -O localdir || echo "DOWN FAILED!" ); exit 1 # 因此, 不管wget是否成功, exit 1都将被执行. wget -q someURL -O localdir || echo "DOWN FAILED!"; exit 1 # 当wget成功后, 忽略 || 后面的语句, 只有失败了,才会echo , 由于echo 成功, 因此也会执行 exit 1 wget -q someURL -O localdir || (echo "DOWN FAILED!" && exit 1) # 这句跟上面的效果一样, 唯一的区别在于exit 1必然会执行而不管echo 语句有没有成功 wget -q someURL -O localdir || (echo "DOWN FAILED!"; exit 1)
换行符 经常会有读取文件的需要, 用的最多的是使用for循环读取文件, 使用的时候需要特别注意文件的换行符, 默认情况下,换行符为空格, 需要使用IFS
指定为换行
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 # 测试文件 cat xx.md this is a test for readline from bash # 读取文件脚本(错误) cat readfromfile.sh # !/bin/bash for LINE in `cat xx.md` do echo $LINE done # 输出, 没有按行输出, 第个单词都占据了一行 this is a test for readline from bash # 正确的脚本 # !/bin/bash IFS_old=$IFS # 将原IFS值保存,以便用完后恢复 IFS=$'\n' # 指定回车为分隔符 for LINE in $(cat xx.md) do echo ${LINE} done IFS=$IFS_old # 恢复原IFS值 # 输出 this is a test for readline from bash # 当然以下两种方式也可以达到逐行输出效果, 大文件请考虑效率 # second cat xx.md | while read line do echo "${line}" done # third while read line do echo "${line}" done < xx.md
变量默认值 有时候定义变量的时候, 经常需要默认值, shell中也有一些比较有趣的表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ {value:=word} # 如果value存在且非null 则返回value 否则把word赋值给value并返回word(=value), 适合场景:如果value没有初始化 可给它赋初值 echo ${value:=word}--->文件的第一行 echo ${value1:=word}--->word 且会把word赋值给value $ {value:+word} # 如果value存在且非null 则返回word 如果value没有设置或为空 则返回value echo ${value:+word}--->word echo ${value1:-word}---> 返回空 因为value1本身就是空 $ {value:?word} # 如果valued存在且非Null, 那就什么也不做。否则,value:word会被发送到标准错误输出,并且程序会退出;如果没有指定word 则输出 value: parameter null or not set echo ${value:?nomessage}---->输出第一行 echo ${value:?nomessage}---->value:nomessage echo ${value:?}----->value: parameter null or not set
参数解析getops 在写脚本的时候,经常需要对参数进行解析,shell毕竟是个脚本语言, 不可能像python等高级语言一样有很完善的参数解析库, getops是bash自带的一个用于参数解析的工具, 但是它只是用于参数解析, 不能对参数进行更高级的操作,比如参数间依赖, 参数判断等
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 function usage() { echo "${0} -m [master/slave] -t [my/db] -s [slave-ip]" echo '' # shellcheck disable=SC2016 echo ' -m: Specify install type: master or slave' # shellcheck disable=SC2016 echo ' -s: If type == master, then must specify opq slave ip' # shellcheck disable=SC2016 echo ' -t: Specify instll opq type: my or db, my is short for mingyuan' echo 'example:' cat << EOF #install opq-db # master # ./auto_install_opq_from_oss.sh -m master -s 127.0.0.1 -t db # slave # ./auto_install_opq_from_oss.sh -m slave -t db # install opq-mingyuan # master # ./install_opq.sh -m master -s 127.0.0.1 -t my # slave # ./install_opq.sh -m slave -t my EOF } while getopts "m:s:t:h" opt # ":m:s:t:h" 如果首位的出现: 表示不打印错误信息 # 上面这句表示: -m -s -t 需要参数 -h 不需要传参 do case ${opt} in m) MODE=${OPTARG} ;; s) SLAVEIP=${OPTARG} ;; t) STYPE=${OPTARG} ;; h) usage exit 0 ;; \?) echo "Invalid option: -$OPTARG" >&2 usage exit 1 ;; esac done # 这里要说明一下 "m:s:t:h" 的含义 # 如果首位(m前)出现 : 表示「不打印错误信息」,也就是说如果需要带参数但没有带时不会打印错误 # 紧邻字母后面的 : 表示该选项接收一个参数, 如果没有带参数的话,则会提示错误
多if时不如使用case 在需要使用法if进行业务判断时, 不防使用case,相对于层层if, 代码会更加清晰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if [[ "$lmode" == "master" ]]; then case $lstype in my) OPQ_UNTAR_DIR_NAME='multiindex_opq_master_mingyuan' ;; db) OPQ_UNTAR_DIR_NAME='multiindex_opq_master' ;; esac PROGRAM='opq_master' else case $lstype in my) OPQ_UNTAR_DIR_NAME='multiindex_opq_slave_mingyuan' ;; db) OPQ_UNTAR_DIR_NAME='multiindex_opq_slave' ;; esac PROGRAM='opq_slave' fi
加载key-value类的配置文件 1 2 3 4 5 6 7 8 9 cat xx key1=value1 key2=value2 cat xx.sh . xx # 这里直接使用. xx即可把xx文件里的key-value引入,后续可直接使用k. echo $key1, $key2 # 注意, 配置文件只能是k=v的形式(多个k=v可以在一行, 使用空格隔开), 其它形式会报错
一行代码: 字符串是否包含子串 1 2 [ -z "${CONSUL_SERVER##*,*} " ] && EXPECT_LEN=3 || EXPECT_LEN=1
for循环打印带空格字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 K8S_CLUSTER=("172.1.52.250 k8s-master-250" "172.1.52.50 k8s-master-50" ) for x in ${K8S_CLUSTER[@]} ; do grep "$x " /etc/hosts > /dev/null || echo "$x " >> /etc/hosts;done 172.1.52.250 k8s-master-250 172.1.52.50 k8s-master-50 for x in "${K8S_CLUSTER[@]} " ; do grep "$x " /etc/hosts > /dev/null || echo "$x " >> /etc/hosts;done 172.1.52.250 k8s-master-250 172.1.52.50 k8s-master-50
未完待续 参考文章: