最近写的shell脚本比较多,记录一些常用命令, 相当于记录一个索引, 以后用时可以快速回忆起来.
#!/bin/bash
#
#!/bin/bash被称为shebang line, 指定执行此脚本文件时使用/bin/bash做为shell解释器程序
很多主流操作系统默认的shell解释器也是bash
# echo $SHELL
/bin/bash
set
#
set命令用来修改shell环境的运行参数, 完整的可定制的官方手册
下面是我常用的几个, 可以合并为如下内容写在脚本开头:
#!/bin/bash
set -uxe
set -o pipefail
set -u
#
执行脚本时, 如果遇到不存在的变量, Bash默认会忽略, set -u可以让脚本读到不存在变量时报错
set -x
#
命令执行前会先打印出来, 行首以+表示, 在调试脚本时非常有帮助
set -e
#
执行脚本时, Bash遇到错误默认会继续执行, set -e使得脚本只要发生错误, 就中止执行
set -o pipefail
#
set -e有一个例外情况, 就是不适用于管道命令, 比如下面的不会退出
#!/bin/bash
set -e
foo | echo a
echo bar
执行的结果为:
a
set.sh: line 4: foo: command not found
bar
set -o pipefail可以解决这个问题, 只要一个子命令失败, 整个管道命令就失败, 脚本就会终止执行
#!/bin/bash
set -eo pipefail
foo | echo a
echo bar
执行的结果为:
a
set.sh: line 4: foo: command not found
单引号''和双引号""
#
双引号会将$var解析成本身的值, 单引号不会解析
# var=1
# echo "$var"
1
# echo '$var'
$var
<< here document
#
一般使用Here Document作为标准输入喂给kubectl apply -f -或者重定向到文件里.
#!/bin/bash
# 标识符或限定符IDENT一般使用EOF表示
COMMAND <<IDENT
this is ...
IDENT
cat <<EOF写入到文件
#
cat一般用来查看文件内容, cat <<EOF可以用来将多行内容打印到标准输出重定向写入到文件里, 这里限定符使用EOF.
cat <<EOF > doc.md
# this is ...
EOF
kubectl apply -f - <<EOF
#
使用kubectl直接不创建文件去apply一个yaml
kubectl apply -f - <<EOF
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf-2
EOF
sed (stream editor)
#
sed全名stream editor, 会流式的一行一行编辑文件, sed手册
下面的修改都是打印到标准输出, 加上-i参数sed -i 'xxx' filename就可以直接更新到文件了
s命令替换字符串
#
结合上面的here document一起演示, 这样就不用再单独创建文件了
将镜像taglatest改为v1.1.1
#
sed 's/latest/v1.1.1/g' - <<EOF
ecr.gobai.top/example:latest
EOF
s表示替换命令,/latest/表示匹配latest,/v1.1.1/表示将匹配到的替换为v1.1.1,/g表示每一行中匹配到的全部替换, 没有g只会替换每一行中的第一个.
只替换行中匹配到的某一个 #
# 替换第1个
sed 's/latest/v1.1.1/1' - <<EOF
ecr.gobai.top/example:latest latest latest
EOF
# 替换第2个
sed 's/latest/v1.1.1/2' - <<EOF
ecr.gobai.top/example:latest latest latest
EOF
# 替换第2个和之后的
sed 's/latest/v1.1.1/2g' - <<EOF
ecr.gobai.top/example:latest latest latest
EOF
只替换部分行字符串 #
# 只替换第2行
sed '2s/latest/v1.1.1/g' - <<EOF
ecr.gobai.top/example:latest
ecr.gobai.top/example:latest
ecr.gobai.top/example:latest
EOF
# 只替换2-3行
sed '2,3s/latest/v1.1.1/g' - <<EOF
ecr.gobai.top/example:latest
ecr.gobai.top/example:latest
ecr.gobai.top/example:latest
EOF
更复杂的涉及很多正则的场景我一般直接丢给
ChatGPT去写, 知道sed可以完成这些任务就可以了!!!
一些sed中常用的正则的语法
#
大部分都是通用的, 在其他需要正则匹配的地方也可以使用, 如在grep -e或grep -E中也可以使用
https://www.gnu.org/software/sed/manual/html_node/Regular-Expressions.html
| 字符 | 含义 |
|---|---|
| 单个普通字符匹配自身 | |
* | 匹配*前面正则表达式的零个或多个匹配项 |
\+ | 类似*, 但是匹配至少一个 |
\? | 类似*, 但是匹配零个或一个 |
\{i\} | 类似*, 但是匹配i个 |
\{i,\} | 类似*, 匹配至少i个 |
\(regexp\) | 将内部的regexp作为一个整体分组 |
. | 匹配任意单个字符, 包括换行 |
^ | 匹配开始处的null字符串 |
$ | 匹配结尾处的null字符串 |
[list] | 匹配方括号中的字符列表中的任意一个 |
[^list] | 匹配非方括号中的字符列表中的任意一个 |
regexp1|regexp2 | 匹配regexp1或regexp2 |
regexp1regexp2 | 匹配regexp1和regexp2的连接结果 |
\digit | 匹配正则表达式中第digit个圆括号\(...\)子表达式 |
\n | 匹配换行符 |
\char | 匹配char字符, char可以是$ * . [ \ 或者 ^ |
一些注意点:
- 虽然
.可以匹配换行符, 但是有的命令是一行一行处理, 所以正则匹配不到换行, 如sed命令
圆括号匹配 #
圆括号括起来的正则表达式所匹配的字符串可以当成变量来使用, 通过\1或\2来引用
将VERSION后面的版本替换
#
V="1.1.1"
sed "s/\(^VERSION:\s*\)[0-9.]\+/\1$V/" - <<EOF
VERSION: 0.0.1
EOF
圆括号
()需要转义\(\), 并且因为有变量$V, 单引号需要改为双引号,^代表行的开始,VERSION:匹配文本字符串,\s*匹配0或多个空白字符,[0-9.]\+匹配一个或多个数字或.,\(^VERSION:\s*\)匹配到了版本号前面的内容作为变量1,\1$V代表将匹配到的所有内容替换为版本好前面的内容+新的版本号$V
echo命令
#
echo -n
#
-n do not output the trailing newline
在结尾处不自动添加换行符
echo -e和echo -E
#
-e enable interpretation of backslash escapes # 激活转义字符
-E disable interpretation of backslash escapes (default)
grep命令
#
grep应该是特别常见的命令了, grep支持从标准输入stdin作为参数, 如echo 'abc' | grep abc, 也可以从命令行输入参数, 如grep abc demo.md
grep -E
#
正则匹配
# str=$(echo '1. aaa\n2. bbb')
# echo $str | grep -E 'a|b'
1. aaa
2. bbb
grep -i
#
忽略大小写
# str=$(echo '1. aaa\n2. bbb')
# echo $str | grep -i -E 'A|B'
1. aaa
2. bbb
grep -v
#
-v, --invert-match # 反向匹配, 选择不匹配的行
Invert the sense of matching, to select non-matching lines.
获取不包含a的行
# str=$(echo '1. aaa\n2. bbb')
# echo $str | grep -v a
2. bbb
grep -n
#
-n, --line-number # 打印匹配到的行在输入中的行号
Prefix each line of output with the 1-based line number within its input file.
打印匹配到的行的行号
# echo $str | grep -n -E 'a|b'
1:1. aaa
2:2. bbb
grep -r
#
-r, --recursive # 递归读取目录下的文件
Read all files under each directory, recursively, following symbolic links only if they are on the command line. Note that if no file operand is
given, B<grep> searches the working directory. This is equivalent to the -d recurse option.
在当前目录递归遍历匹配字符串并打印行号 #
grep -rn "string" .
xargs命令
#
TODO
awk命令
#
awk也是依次处理文件的每一行, 适合处理每一行格式相同的数据
基本用法
# 格式
awk 动作 文件名
# 示例
awk '{print $0}' - <<EOF
abc 123 666
bcd 234 777
EOF
大括号
{}内部是处理当前行的动作,$0代表当前行, 最终效果就是原样打印所有行.awk会根据空格或制表符将每一行分成若干字段, 通过$1$2$3代表每一个字段, 也可以通过awk -F ':' '{print $1}' xxx手动指定每一列之间的分隔符为:
ldd命令
#
ldd可以查看一个可执行文件依赖哪些动态链接库
离线有动态链接库的程序 #
查看jq命令依赖哪些动态链接库,
# ldd $(which jq)
linux-vdso.so.1 (0x00007fff12b9f000)
libjq.so.1 => /lib/x86_64-linux-gnu/libjq.so.1 (0x00007fc42d080000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc42ce00000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc42cd19000)
libonig.so.5 => /lib/x86_64-linux-gnu/libonig.so.5 (0x00007fc42cc86000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc42d0e9000)
如果要离线一个脚本, 只需要将=>后面的文件复制一份打包即可, 这时awk命令就能派上用场了
APP_NAME="jq"
mkdir ${APP_NAME}_archive && cd ${APP_NAME}_archive
mkdir libs
ldd $(which ${APP_NAME}) | awk '{print $3}' | xargs -i cp -L {} libs
这样只需要再把可执行文件也离线, 就可以离线安装运行了
不过还有最后一步, 这些lib文件不适合直接都放入/lib/目录下?, 因为有可能这些lib目录下有和当前程序冲突的版本, 所以直接把程序依赖的lib放在一个目录下然后启动时设置LD_LIBRARY_PATH目录让程序去找正确版本的lib库是更稳妥的.
cp $(which ${APP_NAME}) .
cat <<EOF > app_${APP_NAME}.sh
#!/bin/bash
INSTALL_DIR="/opt/app_archives"
APP_NAME="${APP_NAME}"
export LD_LIBRARY_PATH="\${INSTALL_DIR}/\${APP_NAME}_archive/libs"
\${INSTALL_DIR}/\${APP_NAME}_archive/\${APP_NAME} "\$@"
EOF
chmod +x app_${APP_NAME}.sh
最终的文件如下
# tree .
.
├── app_jq.sh
├── jq
└── libs
├── libc.so.6
├── libjq.so.1
├── libm.so.6
└── libonig.so.5
1 directory, 6 files
安装时, 只需要将${APP_NAME}_archive目录放在/opt/app_archives目录下, 然后创建一个如下的软链即可
ln -nsf /opt/app_archives/\${APP_NAME}_archive/app_\${APP_NAME}.sh /usr/bin/\${APP_NAME}
mc(minio client)
#
设置alias #
mc alias set {NAME} http://minio.lan:9000 {USER} {PASSWORD}
设置匿名用户对某bucket权限 #
权限有download, upload 和 public(download+upload)
设置匿名用户可以下载某个bucket下的文件
# mc anonymous set download minio/app
Access permission for `minio/app` is set to `download`
查看匿名用户对某bucket的权限
# mc anonymous get minio/app
Access permission for `minio/app` is `download`
jq命令
#
jq用来处理json数据还是超级强大的
select过滤数组
#
下面的命令用到了jq中的管道操作|和过滤操作select
# echo '[{"name":"compute","image":"c-image"},{"name":"log"}]' | jq '.[] | select(.name == "compute") | .image'
"c-image"
有时不想要字符串两边的双引号, 这时就需要用到上面用到的sed命令了, 思路是正则匹配整个字符串, 然后双引号中间的使用圆括号匹配作为变量\1, 最后将整个字符串替换为\1即可
# str=$(echo '[{"name":"compute","image":"c-image"},{"name":"log"}]' | jq '.[] | select(.name == "compute") | .image')
# echo $str | sed 's/^"\(.*\)"$/\1/'
c-image
tar命令
#
压缩文件 #
tar -czvf name.tar.gz name
使用pigz加速压缩
#
如果没安装pigz需要先安装apt install pigz -y
tar -I pigz -czvf name.tar.gz name
解压文件 #
tar -xzvf name.tar.gz
docker命令
#
导出镜像到压缩包 #
docker save -o all.tar image-a:1.0.0 image-b:1.0.0 image-c:1.0.0
从压缩包导入镜像 #
docker load < all.tar
TODO #
- 看一下 the art of command line 然后融入进这片文档
- 增加 shell 常用的控制循环和条件判断语法