Shell 脚本在我们日常开发和学习都有举足轻重的地位,比如看一些开源项目,比如项目中的各式各样的脚本,对于促进生产力工具有很大帮助!而且shell最大的好处就是非常的方便!bash是一个非常成熟的脚本语言,基本上具有现代脚本语言的所有特性,所以你可以通过bash实现各种各样的脚本!
我们日常开发中使用mac作为开发机的话需要考虑和linux命令兼容的问题,所以需要在mac上替换成gun相关的命令!
1、命令小技巧 1、-x
命令进行跟踪调试执行 注意set -x
是开启trace,set +x
是关闭trace
1 2 3 4 5 6 7 8 9 # !/bin/sh num1=10 num2=20 if (($num1 <= $num2)); then echo num1 lesser equal num2 else echo num1 greater num2 fi
执行:
1 2 3 4 5 6 ➜ note git:(master) ✗ sh -x /Users/fanhaodong/Desktop/project/test.sh + num1=10 + num2=20 + (( 10 <= 20 )) + echo num1 lesser equal num2 num1 lesser equal num2
2、-c
命令 (执行命令参数) 1 2 3 4 5 6 7 ➜ note git:(master) ✗ sh -c << EOF " dquote> echo hello world dquote> echo hello world2 dquote> " heredoc> EOF hello world hello world2
这种经常会在网上下载一个脚本然后直接执行,可以 sh -c "curl xxxx.sh"
3、使用set变量 一般就是 set -ex ,或者执行的时候 bash -ex
1 2 3 4 5 6 7 8 9 10 # !/bin/sh # -v Print shell input lines as they are read . # -x Print commands and their arguments as they are executed. # -e Exit immediately if a command exits with a non-zero status. set -ex echo "hello world" exit 1
执行
1 2 3 4 5 6 ➜ makefile git:(master) ✗ sh ./main.sh + echo 'hello world' hello world + exit 1 ➜ makefile git:(master) ✗ echo $? 1
帮助可以看: sh -c "help set"
2、语法小技巧 1、引用变量 一般推荐正确用法是,变量使用 ""
双引号引用,其次就是变量使用 ${}
进行引用!very good!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 使用 ${} 引用变量,拒绝歧义 ➜ ~ data="hello" ;echo $dataa ➜ ~ data="hello" ;echo ${data}a helloa # 字符串用 "" 引用 ➜ ~ data="hello " ;echo "${data}a" hello a ➜ ~ data=hello ;echo "${data}a" helloa # 单引号不会进行变量赋值 (注意) ➜ ~ data=hello ;echo '${data}a' $ {data}a
2、 $( cmd )
和 `cmd` 执行命令 1 2 3 4 ➜ ~ echo $(uname) Darwin ➜ ~ echo `uname` Darwin
3、 cat [>>|>] [file] << EOF .... EOF
写入文件 如果重定向的操作符是<<-,那么分界符(EOF)所在行的开头部分的制表符(Tab)都将被去除。这可以解决由于脚本中的自然缩进产生的制表符。
1 2 3 4 5 6 7 8 9 10 11 12 ➜ test cat > glide.yaml << EOF heredoc> name: tom heredoc> age: 10 heredoc> hobby: heredoc> - football heredoc> EOF ➜ test cat glide.yaml name: tom age: 10 hobby: - football
4、管道符 和 xargs
1. 管道符 管道符作用就是把上一个命令的标准输出 作为下一个命令个标准输入
1 2 ➜ echo "hello wrold" | python -c 'import sys; print(sys.stdin.read())' hello wrold
2. xargs 1 .xargs
的作用就是把上个命令的标准输出 作为 下一个命令的参数
1 2 ➜ echo "hello wrold" | xargs python -c 'import sys; print(sys.argv)' ['-c', 'hello', 'wrold']
如果想替换分隔符号需要输入参数 -d
, 同时你想打印所执行的命令 -t
1 2 3 4 5 6 7 ➜ ~ echo "hello:wrold" | xargs -t -d ':' python -c 'import sys; print(sys.argv)' python -c 'import sys; print(sys.argv)' hello 'wrold'$'\n' ['-c', 'hello', 'wrold\n'] # 上面带\n的原因是echo 默认换行,需要 ➜ ~ echo -e "hello:wrold\c" | xargs -d ':' python -c 'import sys; print(sys.argv)' ['-c', 'hello', 'wrold']
xargs 是并行执行的,可以通过如下测试**, -P
等于0表示不限制进程数**,默认是1, -n
等于1表示参数按每一个进行拆分 这个命令相当棒,就是可以并行执行!
1 2 3 4 5 ➜ echo "hello world" | xargs -P0 -n1 sh -c 'echo "start $$"; sleep 5s; echo "end $$"' start 21377 start 21378 end 21377 end 21378
-I
,可以替换命令行参数1 2 3 4 5 ➜ ~ echo "hello wrold" | xargs echo hello wrold ➜ ~ echo "hello wrold" | xargs -I {} echo {} hello wrold
5、特殊变量 $0
: 当前脚本的文件名 $[n]
: 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。$#
: 传递给脚本或函数的参数个数。$*
: 传递给脚本或函数的所有参数$@
: **传递给脚本或函数的所有参数(推荐使用这个)**,当使用 ""
双引号引用是 $*
会变成字符串而不是数组$?
: 上个命令的退出状态,或函数的返回值 。一般情况下,大部分命令执行成功会返回 0,失败返回 1。$$
: 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。$!
子进程ID6、[[]]
和 []
标准 以及基本语法规范 1 2 3 4 5 6 7 8 9 10 11 12 name="xiao_li" # 比较 = / != if [ "$name" = "xiao_li" ]; then echo "1(bash运算符). name is xiao_li"; fi if [[ "$name" = "xiao_li" ]]; then echo "1(bash运算符). name is xiao_li"; fi age=10 if [ "$name" = "xiao_li" ] && [ "$age" -eq 10 ]; then echo "3(sh逻辑逻辑). name is xiao_li and age is 10"; fi if [[ "$name" = "xiao_li" && "$age" -eq 10 ]]; then echo "4(bash逻辑逻辑). name is xiao_li and age is 10"; fi if [[ "$name" == xiao_* ]]; then echo "5(bash通配符). xiao_li has prefix xiao_"; fi if [[ "$name" =~ ^xiao_[a-z]+* ]]; then echo "6(bash正则). xiao_li reg match ^xiao_[a-z]+* "; fi
总结: 大部分情况下 []
都可以满足需求,如果使用到通配符、正则等需求再使用[[]]
,其次关于逻辑运算推荐使用 [] && []
这种方式,除非有复杂的逻辑运算需求!
7、/bin/sh 与 /bin/bash 的区别 /bin/sh 与 /bin/bash 的区别
3、获取命令结果 $(cmd)
有两种写法,一种是 $()
这个并不是所有的shell都支持,但是比较直观, 另外一种是 "``"
(它可是适用更多的平台)
1 2 3 4 5 # !/bin/sh echo `ls -a /Users/fanhaodong/note` echo $(ls -a /Users/fanhaodong/note)
输出:
1 2 . .. .DS_Store 1714.jpg docker-rocketmq-cluster gridea-home hexo-home note pdf vuepress-starter . .. .DS_Store 1714.jpg docker-rocketmq-cluster gridea-home hexo-home note pdf vuepress-starter
4、输入输出重定向 2>&1
使用 程序中经常有,标准输出,但是还有错误输出,因此需要合并到一个流中
其实Go的程序中正执行脚本的时候可以指定,标准输出和错误输出
1 2 3 command := exec.Command(shell, "-c" , cmd) command.Stdout = os.Stdout command.Stderr = os.Stderr
使用的时候:
默认为标准输出重定向,与 1> 相同 2>&1
意思是把 标准错误输出 重定向到 标准输出.&>file
意思是把标准输出和标准错误输出 都重定向到文件file中例如:
command >out.file 2>&1 &
command >out.file是将command的标准输出重定向到out.file文件,即输出内容不打印到屏幕上,而是输出到out.file文件中。2>&1 是将标准出错重定向到标准输出,这里的标准输出已经重定向到了out.file文件,即将标准出错也输出到out.file文件中。最后一个&
,是让该命令在后台执行。
参考 https://www.cnblogs.com/caolisong/archive/2007/04/25/726896.html
5、If语句 if 其实就是test
命令
1、格式 换行写 1 2 3 4 5 6 7 if [ condition ]; then # body elif [ condition ]; then # body else # body fi
2)非换行写
1 if [ -f "/Users/fanhaodong/note/note/Makefile1" ]; then echo 111 ; echo 222 ;elif [ -f "/Users/fanhaodong/note/note/README.md" ]; then echo 333 ; echo 4444 ; else echo 555 ; echo 666 ; fi
2、结果获取/判断 结果输出0 ,表示为真,可以通过$?
来获取结果
3、例如调试条件 1 2 3 ➜ note git:(master) ✗ test "abc"!="def" ➜ note git:(master) ✗ echo $? 0
4、测试文件是否存在 如果你要判断一个文件是否存在,只需要 -e
即可,输出0
表示文件存在 (不在判断类型的时候推荐使用这个) 如果你要判断一个文件是否为文件夹,并且判断是否存在,只需要 -d
即可 如果你要判断一个文件是否为常规文件
,并且判断是否存在,只需要-f
即可 -L filename 如果 filename为符号链接,则为真 1 2 3 4 5 [root@019066c0cd63 ~]# ls -al lrwxrwxrwx 1 root root 5 Mar 1 09:49 c.txt -> a.txt [root@019066c0cd63 ~]# [ -L "./c.txt" ] [root@019066c0cd63 ~]# echo $? 0
-r filename 如果 filename可读,则为真 -w filename 如果 filename可写,则为真 -x filename 如果 filename可执行,则为真 -s filename 如果文件长度不为0,则为真 -h filename 如果文件是软链接,则为真 1 2 3 4 5 6 ➜ note git:(master) ✗ [ -f "/Users/fanhaodong/note/note/Makefile" ] ➜ note git:(master) ✗ echo $? 0 ➜ note git:(master) ✗ [ -f "/Users/fanhaodong/note/note/Makefile1" ] ➜ note git:(master) ✗ echo $? 1
5、字符串操作 字符串推荐加 ""
进行定义
判断字符串是否为空 -z
(zero)么 1 2 3 4 5 6 7 # !/bin/sh str="" if [ -z "${str}" ]; then echo str is empty fi # str is empty
2)判断两个字符串是否相同
1 2 3 4 5 6 7 8 9 10 11 12 # !/bin/sh str1="str" str2="str2" if [ "$str1" = "$str2" ]; then echo str1 is equal str2 else echo str1 is not equal str2 fi # str1 is not equal str2
4、测试一个命令是否存在 command -v $#
1 2 3 4 5 6 7 8 # !/bin/sh cmd=go if [ `command -v $cmd` ]; then echo $cmd command is exists else echo $cmd command not exists fi # go command is exists
5、获取字符串长度 ${#var}
1 2 3 4 5 6 7 8 # !/bin/sh str="hello " str1=hello echo str 的长度是 ${#str} echo str1 的长度是 ${#str1} # str 的长度是 8 # str1 的长度是 5
6、数字比较 -eq 等于 -ne 不等于 -gt 大于 -ge 大于等于 -lt 小于 -le 小于等于 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # !/bin/sh num1=10 num2=20 if (($num1 <= $num2)); then echo num1 lesser equal num2 fi if [ $num1 -le $num2 ]; then echo num1 lesser equal num2 fi # num1 lesser equal num2 # num1 lesser equal num2
7、shell脚本中if判断’-a’ - ‘-z’含义 https://blog.csdn.net/tootsy_you/article/details/95597376
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 # !/bin/bash # # 1. 定义数组 arr=("00" "11" "22" "33" "44") # 2. 遍历数组 for elem in "${arr[@]}"; do echo "elem: ${elem}" done # 数组在shell中定义和map差不多,map也可以使用数组进行表达,key=index, value=elem for index in "${!arr[@]}"; do echo "elem: ${arr[index]}" done # 标准for 循环 for ((index = 0; index < ${#arr[@]}; index++)); do echo "elem: ${arr[index]}" done # 3. 添加元素 arr+=("55" "66") echo "len(arr) = ${#arr[@]}" # 4. 元素切片 echo "arr[2:4] = ${arr[*]:2:2}" # 5. 判断元素是否存在 # $1 =want # $2 ...=arr # 注意shell函数参数没有数组的概念,只能说你把它展开成多个参数 # 注意 $@ 为参数, $# 为参数长度 function contains() { # $0 为执行文件 # $1 ... 为参数 want="$1" for elem in "${@:2}"; do if [ "$elem" == "$want" ]; then return 0; fi done return 1 } if contains "111" "${arr[@]}"; then echo "contains 111" else echo "not contains 111" fi if contains "11" "${arr[@]}"; then echo "contains 11" else echo "not contains 11" fi
7、switch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # !/bin/bash name="$1" case "$name" in "11 222"| "222 333") echo "num is: 11 222| 222 3333" ;; 44) echo "name is: 44" ;; *) echo "num is(*): $name" ;; esac
输出:
1 2 3 4 5 6 ➜ script git:(master) ✗ bash -e string.sh "11 222" num is: 11 222| 222 3333 ➜ script git:(master) ✗ bash -e string.sh "11 222 3333" num is(*): 11 222 3333 ➜ script git:(master) ✗ bash -e string.sh "44" name is: 44
字符串操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # !/bin/bash name="xiao_min" # 移除前缀 suffix=${name#"xiao_"} echo "suffix: $suffix" # suffix: min # 移除后缀 prefix=${name%"$suffix"} echo "prefix: $prefix" # prefix: xiao_ # 字符串替换 name2=${name/"min"/"li"} echo "name2: $name2" # name2: xiao_li
getopt 实际中我们写一些复杂的脚本(命令行工具)都需要解析参数,shell提供了getopts
和 getopt
命令可以帮助实现命令行解析
1 2 3 4 ~ type getopts getopts is a shell builtin ~ type getopt getopt is /usr/bin/getopt
-o
或者 --option
表示短选项
-l
或者--longoptions
表示长选项,多个参数用 ,
进行分割
选项会有 必须 、可选 、Flag 三种状态,是通过申明 :
来表示
required 必须:参数定义后面需要跟:
, 必须的意思就是我定义 required 的参数那么这个参数后面一定得跟东西,不是必须传递这个参数,例如 1 2 3 4 5 6 7 8 9 ➜ ~ getopt -o 'a:' -- -a getopt: option requires an argument -- a -- ➜ ~ getopt -o 'a:' -- -a 1 -a '1' -- ➜ ~ getopt -o 'a:' -- 111 222 -- '111' '222'
optional 可选:参数定义后面跟::
, 解释同上面,optional 参数就是你后面可以不跟东西,但是参数值需要附加在值 1 2 3 4 5 6 7 8 9 10 11 12 13 ➜ ~ getopt -o 'a::' -- -a 1 -a '' -- '1' ➜ ~ getopt -o 'a::' -- -a1 -a '1' -- ➜ ~ getopt -o 'a::' -- -a -a '' -- ➜ ~ getopt -o '' -l 'name::' -- --name --name '' -- ➜ ~ getopt -o '' -l 'name::' -- --name='111' --name '111' -- ➜ ~ getopt -o '' -l 'name::' -- --name --name '' --
flag : 参数后面啥也不跟
例如 -o 'abc:d::e'
表示 a、b不接收参数,c必须要跟一个参数,d为可选参数,e不接收参数
注意:optioanl 参数值赋值有点奇葩,这个也是必然的不然无法区分出来optioanl 和 required,例如 短选项需要直接附加在值后面,长选项需要用=
来附加值
getopts getopts 是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 # !/bin/bash # getopts ":ab:c:" opt # OPTIND: 选项索引 option index, 下一个要处理的元素位置, 初始化值是1, 大部分场景也不会用到 # OPTARG: 选项参数 option arg echo "args: $*" echo "OPTIND=[$OPTIND]" while getopts ":ab:c:" opt; do case $opt in a) echo "this is -a option. OPTARG=[$OPTARG] OPTIND=[$OPTIND]" ;; b) echo "this is -b option. OPTARG=[$OPTARG] OPTIND=[$OPTIND]" ;; c) echo "this is -c option. OPTARG=[$OPTARG] OPTIND=[$OPTIND]" ;; *) echo "there is unrecognized parameter." exit 1 ;; esac done
执行:
1 2 3 4 5 6 7 ➜ vscode git:(master) ✗ bash run.sh -a -b1 -c 1 -c2 args: -a -b1 -c 1 -c2 OPTIND=[1] this is -a option. OPTARG=[] OPTIND=[2] this is -b option. OPTARG=[1] OPTIND=[3] this is -c option. OPTARG=[1] OPTIND=[5] this is -c option. OPTARG=[2] OPTIND=[6]
getopt 命令 mac的话需要: brew install gnu-getopt
帮助文档:https://linux.die.net/man/1/getopt
getopt既然是命令,说白了,我们可以怎么处理呢?很简单
1 2 3 4 5 6 7 8 # a为flag # b为required 如果需要带`-d` 参数那么后面一定要附加值 # c为optional # help 为flag # size为optionl # name为required ➜ ~ getopt -o 'ab:c::d' -l 'help,size::,name:' -- -a --name '1' -b '1111' -c1111 --size='1111' -a --name '1' -b '1111' -c '1111' --size '1111' --
现代编程语言 - bash shell bash 和 sh的区别 bash shell 是一门现代的编程语言
sh 仅支持一些简单的逻辑判断、循环等,并不支持一些丰富的内置能力,比如数组、数学计算、条件判断支持正则/通配符等一些高级特性!
控制逻辑 条件控制 1 2 3 4 5 6 7 8 9 10 11 12 name="xiao_li" # 比较 = / != if [ "$name" = "xiao_li" ]; then echo "1(bash运算符). name is xiao_li"; fi if [[ "$name" = "xiao_li" ]]; then echo "1(bash运算符). name is xiao_li"; fi age=10 if [ "$name" = "xiao_li" ] && [ "$age" -eq 10 ]; then echo "3(sh逻辑逻辑). name is xiao_li and age is 10"; fi if [[ "$name" = "xiao_li" && "$age" -eq 10 ]]; then echo "4(bash逻辑逻辑). name is xiao_li and age is 10"; fi if [[ "$name" == xiao_* ]]; then echo "5(bash通配符). xiao_li has prefix xiao_"; fi if [[ "$name" =~ ^xiao_[a-z]+* ]]; then echo "6(bash正则). xiao_li reg match ^xiao_[a-z]+* "; fi
循环语句 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 # !/bin/bash # 以下全部代码仅支持在bash中使用! # {1..5}迭代器生成[1-5]的数组 for elem in {1..5}; do echo "[for-range] elem: $elem" done arr=(1 2 3) arr+=(4) # append arr[0]=11 # set echo "arr[0] = ${arr[0]}" # get # arr - size length=${#arr[@]} echo "arr length = ${length}" # foreach for elem in "${arr[@]}"; do echo "[foreach] elem: $elem"; done # for index (需要bash>=4.0, 支持 associative arrays) for index in "${!arr[@]}"; do echo "[for-index] index: $index, value: ${arr[$index]}"; done # for for ((x = 0; x < length; x++)); do echo "[for] index: $x, value: ${arr[$x]}" done # while index=0 while [ $index -lt "$length" ]; do echo "[while] index: $index, value: ${arr[$index]}" index=$((index + 1)) done # break for elem in "${arr[@]}"; do if [ "$elem" -gt 10 ]; then echo "[break] $elem > 10" break fi done
数据结构