同步操作将从 deepinwiki/wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
管理系统,难免要用到一些脚本,因为很多命令要输入很多遍,难免会记不起那么多,如果每次要完成某样任务,都要重新写一遍命令,那是非常痛苦的事情。可以把shell脚本,看作是一份管理文档,它记录你完成某样任务需要执行的命令步骤。当然,这份文档,还有自动运行的效果。
本文主要围绕 linux 中的常见的 bash shell 展开。
脚本语言和一般的编程语言比起来,要简化很多东西,因为它是关注特定领域的特殊语言,它就是为了把系统管理员日常输入的命令给统一化,程序化而已。因此它的基础,首先是众多的linux命令,然后才是脚本语言特有的一些简单的语法结构。
!!
: 执行上一条命令!str
: 匹配 str 的上一条命令!N
: 第 n 条命令!-N
: 上 n 条命令!$
: 上一条命令最后的参数!*
: 上一条命令的所有参数!!:p
: 回显(但不执行)上一条命令!!:1
: 上一条命令的第一个参数!!:s^old^new
: 执行上一条命令,但先替换掉 old 为 newcd -
: 上一个目录cd ~
: 用户家(home)目录pushd /
: 存档当前目录,并跳转到指定目录popd
: 恢复存档中的目录a | b
: a 命令的输出是 b 命令的输入a > file
: a 命令的输出导出到 file 文件a >&0
: a 命令的输出导出到标准输入(0 标准输入,1 标准输出,2 标准错误)a 2>&0
: a 命令的错误导出到标准输入a >&0; b <&0
: 约等于 a | ba | tee /dev/tty | b
: tee 将 a 的输出保存到 file 文件,然后转发给 ba | xargs b
: a 的输出作为参数传递给 b 命令mkfifo pipe
: 父创建echo "xxx" > pipe &
:子后台写入read var < pipe
: 父读取[ab]
: 匹配 a 或者 b,即中括号指定字符集[!ab]
:上面的反义,除了在字符集中出现的字符都匹配[a-z]
: 匹配 a 到 z 的所有字符,包括 az[a-z]*
: 组合,表示匹配任意字符集中的 n 字符hi=x; # hi 是变量,x是值,变量能存储值。
hello=$hi; # $hi ${hi} 这两种写法都是获取变量的值
# 变量可以不先设置值,默认为空
hello=${hi} #同上
#特别注意
# shell变量是一种简单文字替换的规则,如:
a='x y';
b=$a; #$a被简单替换成b='x y'
unset a;#取消a变量的设置
b=$a; #等价 b= ,这个奇怪的结果在有些就会出现问题,因此最好用"$a"来取代$a,这样就算$a等于空,式子仍然等于b="",没那么怪异。
# 因此很多时候需要把变量放进双引号内
b="$a" # 建议这种写法
a=(1 2 3 4) # 创建数组
b=${a[0]} # 获取数组第一个元素
b=$[a[0]] # 结果同上
let b=a[0] # 结果同上
a=(*.pdf) # 当前目录下所有 pdf 文件组成的数组
echo ${a[@]} # 返回所有成员,注意成员有空格时要放入字符串中
echo ${a[*]} # 返回一个包含所有成员的字符串
b=("${a[@]}") # 拷贝数组
for i in "${a[@]}";do echo $i; done # 枚举数组
echo ${#a[@]} # 数组长度
echo ${!a[@]} # 数组下标
a+=(5) # 添加成员
let hi=1+1; #有返回结果的式子叫表达式,如后半部分hi=1+1等于2. let hi=1+1这个式子整体是个语句,它表示自动计算表达式部分,因而hi=2。
a=1+1; # 没有let的结果是把1+1当作字符串来处理,而不是表达式,因而echo $a 输出 1+1 ,千万要注意shell这个特点,一定要用本节表达式的固有语法来写表达式
a=$((hi+=1)); #$双括号内是表达式.
a=$[1+1]; #同上
a=$(ls); #$单括号内是命令,取得命令的输出结果
# 加减乘除、取余( + - * / % )的结果是整数
# 在括号内的表达式,比外面书写要自由一些,可以添加空格
规范写法说明:
# 字符串
a=1 # 注意不要多余空格
a="a b" # 用字符串包裹,因为可能有空格
b="$a"
# 表达式
a=1
let c=a+1
c=$[a+1]
# 奇怪的技术
a=1
b=a # b='a'
let c=b # c=1 会递归的计算出结果
d=$b # d='a' 保留原样
e=$[b] # e=1
浮点小数:
# shell 只支持整数,但是可以通过bc命令来计算浮点
let a=$(echo "scale=2;5*9.9/3.14"|bc) #scale表示除法计算保留2位小数。
# 支持运算 + - * / % ^幂 sqrt()开平方
# 支持逻辑 > < >= <= == != && || !
# 支持 read()读取 length()有效长度 scale()精度
# bc -l 扩展以下运算
# s(弧度) 正弦
# c(弧度) 余弦
# a(弧度) 反正切
# l() 自然对数
# e() 指数函数
# j() n阶
# 可以自定义一个函数来使用bc
fbc(){ echo "scale=2;$1"|bc -l; }
fbc "3*5.7^3*sqrt(3+69%10)/3.14"
[ -d x ] #注意中括号内侧都要有空格,表示x是空字符串就返回0,而0在shell中代表true,即为真。
# 判断表达式是程序智能的基础,它能根据不同情况,配合流程语句选择不同的执行路线。
[[ -d x ]] # 双中括号比单的更通用
关键字 | 分类 | 意义 |
---|---|---|
-d | 文件 | 是否目录 |
-e | 文件 | 文件、目录、设备等是否存在 |
-f | 文件 | 文件 |
-p | 文件 | 命名管道 |
-L | 文件 | 链接 |
-r | 文件属性 | 读 |
-w | 文件属性 | 写 |
-x | 文件属性 | 执行 |
-nt | 修改时间 | [ a -nt b ] a比b新 |
-ot | 修改时间 | [ a -ot b ] a比b旧 |
-z | 字符串 | 空 |
-n | 字符串 | 非空 |
== | 字符串 | a == b 相等 |
!= | 字符串 | a != b 不等 |
-eq | 数值 | 相当于 == |
-ne | 数值 | != |
-ge | 数值 | > |
-gt | 数值 | >= |
-le | 数值 | < |
-lt | 数值 | <= |
>、<
这些符号不能直接使用,因为shell会把它视为管道符,而不是大于小于。并且也不支持>= <=
将几个判断组合起来的表达式,称为逻辑表达式:
逻辑表达式 | 例子 | 意义 |
---|---|---|
&& |
[ -r a ] && [ -w a ] |
a可读且可写 |
! |
! [ -f a ] |
a不是文件 |
|| |
[ -f a ] || [ -d a ]
|
a是文件或目录 |
[[ -fa || -d a ]]
双中括号形式,更加智能好用。let a=1; #计算表达式
test -z a; #类似[ -z a ]
exit 1; #退出shell脚本,并返回1,非0表示出现错误。
. file # 执行file内容,等价source file
export a; # 让被调用的shell命令继承定义的a变量
ls x*; # x* 表示让shell从当前工作目录匹配x开始的路径
通配glob匹配规则:
*
匹配0到n个字符?
表示匹配一个字符[字符]
一次匹配括号内任意一个字符[!字符]
一次匹配非括号内的一个字符{字符串2,字符串2}
一次匹配大括号内中对应的一个字符串{1..100}
同上,表示1到100的序列cat < a; #重定向读取a文件,并通过cat输出到标准输出
重定向:
/dev/stdin
标准输入,文件描述符0/dev/stdout
标准输出,文件描述符1/dev/stderr
标准错误输出,文件描述符2>
输出重定向<
输入重定向1>&0
等价重定向>>
追加输出重定向<<
追加输入重定向|
管道,a|b 等于 a > /dev/stdin; b < /dev/stdin;变量中的变量:
let a=1
x='long$a' #x是单引号字符串long$a,而不是long1
#怎样通过x获得a的值?
# 通过二次解析shell参数命令:eval
$(eval echo $x)
#1. shell将eval echo $x解析为 eval echo long$a
#2. eval 将参数再次提交给shell解析,eval echo long1
#3. eval执行命令,得long1
# 注意,如果x只是一个使用a变量的表达式,可以直接计算,而不需要经过shell进行符号解析
x='a+1'
$[$x] #直接计算a+1 => 1+1 => 2
变量空值短语:
let x="";
a=${x:-string} # 如果x是空,用string取代它
a=${x:=string} # 同上,并设置x=string
a=${x:+string} # 如果x不空,string取代它
a=${x:?string} # 如果x为空,报错string并退出
变量模式匹配短语:
x="asIas"
a=${x%a*s} # 从右去除最短匹配,得asI
a=${x%s*s} # 从右去除最长匹配,得a
a=${x#a*s} # 从左去除最短匹配,得Ias
a=${x##a*a} # 从左去除最长匹配,得s
脚本环境自带许多有用的默认变量。
符号 | 值 |
---|---|
~ | 用户目录路径 |
~user | 指定用户目录 |
~+ | 当前目录,类似$(PWD) |
$# | 参数总数 |
$? | 命令的返回值 |
$0 | 命令本身 |
$1 | 第一个参数 |
$2 | 第二个参数,依此类推 |
$* | 所有参数的字符串 |
$@ | 参数数组 |
if 条件; then 命令; fi
if 条件; then 命令1; else 命令2; fi
if 条件1; then 命令1; elif 条件2; then 命令2; ... fi;
case word in 模式1) 命令1;; 模式2)命令2;; ... esac
while 条件;do 命令;done
for 变量 in 数组; do 命令; done
函数(){ 命令; }
#注意大括号内侧空格ls -lh <(ls)
: 显示 /dev/fd/63 -> 'pipe:[410657]'编辑内容文件test.sh:
#!/bin/bash
#脚本文件第一行的标准写法如上,表示调用/bin/bash脚本解释器
# 注意防止$1$2为空,需要将它放进引号内
if [ -f "$1" ] && [ -f "$2" ];then
echo newfile:$1$2;
cat $1 $2 > $1$2; #合并参数1,参数2的文件内容
fi
# 函数有自己独立的参数 $1 等变量
help(){
cat <<EOF
使用说明:
-a 输出-a
-b xxx 输出 xxx
-h 显示本页信息
EOF
local a=1 # 定义局部变量
}
let index=1; #定义保存序号的变量,从1开始编号
for var in "$@";do #读取命令参数数组
echo arg$index:$var; #打印位置和参数
index+=1; #位置+1
done
while [ -n "$1" ];do #如果参数1存在,循环
case "$1"
in
-h) help;shift 1;; #shift 1 跳过一个参数
-a) echo $1;shift 1;; #匹配 -a 选项
-b) echo $2;shift 2;; #匹配 -b 选项
--) shift;break;; #参数结束,跳出循环
-*) echo 没有$1选项。;exit 1;; #没有定义的选项,退出shell程序
*) break;; # 其他情况,跳出循环
esac
done
while :
do
echo 无限循环
done
执行脚本:
chmod +x test.sh #添加执行权限给脚本文件
./test.sh #linux中调用不在$PATH路径变量下的可执行程序,都要给出具体路径。如./xxx 而不能省略为 xxx
#!/usr/bin/env bash
# 脚本第一行#!开头,下面会打开指定程序(即脚本解释器)
# /usr/bin/env 是一个路径固定的程序,它能够依据环境变量
# 再找到 bash 然后打开
# 有一些是这样 #!/bin/sh 。sh 一般会链接到 bash
# 没有指定解释器,那么也可以,系统一般都会找到
# 但用户可能配置了不同的 shell,那么就有问题
模式匹配:
*
:任意字符串?
:一个字符[...]
:字符集,如[ab]
表示 a 或 b
[^...]
:取反a="aabbaacc"
${#a} # 字符串长度
${a:1:3} # 子串 tri
m=? # 模式,可以使用通配符
${a#m} # 从头开始,最短匹配,并删除
${a##m} # 从头开始,最长匹配,并删除
${a%m} # 从后开始,最短匹配,并删除
${a%%m} # 从后开始,最长匹配,并删除
${a#+(a)} # abbaacc
${a##+(a)} # bbaacc
${a%+(c)} # aabbaac
${a%%+(c)} # aabbaa
${a/m/"x"} # 单次匹配,并替换
${a//m/"x"} # 多次匹配,并替换
${a/a/x} # xabbaacc
${a//a/x} # xxbbxxcc
let a=1 # 等号要连着
let a=2 # 想修改值时可以重复设置
a=1 # 有 let 将视为表达式,没 let 视为字符串 '1'
let b=a+a # 如果要让变量按数字理解,得 2
b=$[a+a] # 或这样。用 $[a+a] 计算,得 2
b=a+a # 得 'a+a'
b=$a+$a # 得 ‘1+1’
let b=$a$a # 如果只是取值,他会按字符串拼接到一起, 得 '11'
b="$a" # 变量在字符串中也是可以使用,当 $a 为空时,得到空字符串。比如在判断中他们是不同的
unset b # 设置空值
export c=1 # 设置环境变量,它不但在当前脚本有效,子命令也有效
local d=1 # 在函数内定义局部变量
# 条件
# 条件 [ 判断其实也是一个命令,所以不要紧贴着参数,即 [ a 而不是 [a
# 虽然人眼看上去能区分,但 bash 语法没那么精细
# [ x ] 单个条件,注意中括号不要连着条件
# ! [ x ] 否定条件,注意 ! 号不要连着条件
# [[ ! x ]] 或者这样
# [[ x && y ]] 多个条件
if [ 1 -eq 1 ]; then echo $?;else echo $?;fi
# 注意 if 语句本质是几个语句连在一起,所以需要 ; 分割,除非不在一行
set -x # 显示当前执行的语句
set +x # 关闭
set -e # 遇到错误(语句返回非0)立即退出
set +e # 只提示,接着执行后续语句
command || exit 1; # 遇到错误立即退出
set -eo pipefail # 包括管道任意位置的错误也将立即退出
pipe=$(mktemp -u) # 创建临时文件
trap "rm $pipe" EXIT # 任何情况退出都删掉命名管道
mkfifo $pipe # 创建命名管道,进程间通信
echo sss | cat >$pipe & # 后台任务,写入管道
read msg < $pipe && echo $msg # 读取管道内容,这套操作可以跨进程
a | b | c : a 的输出进入 b 的输入,b 的输出进入 c 的输入。系统实际上使用子进程来执行,所以 a b c 是当前程序的子程序。
export E 环境变量可以从当前程序传到 abc 但,他们无法修改,修改后的结果无法传递回父进程。这是一个单向通信的方式。
为了双向通信,需要使用到一个叫命名管道的技术,这是 linux 进程通信的技术。
mkfifo 命令创建一个管道文件。这有点类似匿名管道, a | b | c,一个地方写入,另一个地方读取。但 a | func 是不行的,因为 func 本身又变成子程序了。我们要让子程序和当前程序通信,需要 a > pipe。 但写入的时候,就会等待另外的地方读取,程序就卡住,阻塞了。
所以,需要使用后台执行的方式, a > pipe & 。只需要加 & 在一行命令后面,它就在后台执行,不会阻塞当前程序。
要如何读取管道数据,到变量并没有想像中那么简单,因为变量不接受管道输入 var < pipe 是不合法的。这时需要使用一个 read 命令,它可以读取管道数据,并填充到变量中。
但 read 一次只能读一行。所以需要我们使用循环语句来读取所有数据。
问题来了,如何用循环正确读取管道数据?
# 命名管道
trap 'rm -v pipe' EXIT
mkfifo pipe
man while | cat > pipe &
while read line
do
echo $line
done < pipe
# 使用进程替换技术
while read line
do
echo $line
done < <(man while)
匿名管道的输出其实有两个,一个是正常消息,一个是错误消息。有一些命令的错误消息并不是那么容易捕获。
# time 计时信息通过错误通道传递
# tr 转换成大写
# 但代码不生效
time sleep 1 2>&1 | tr 'a-z' 'A-Z'
# 需要大括号包裹起来作为一个语句段来重定向
# time 实际将错误传递给 sleep 了,所以无法正常方式捕获
{ time sleep 1; } 2>&1 | tr 'a-z' 'A-Z'
管道之间是通过读取管道来执行,但是有些命令,它并不读取管道,它接受普通的参数。这时候就需要 xargs 命令,它读取管道,然后将其转化为参数,提供给下一个命令。
# 无效,因为 echo 不接受管道输入
echo 1+1 |echo
# xargs 将读取的所有输入替换成参数序列
echo 1+1 | xargs echo # 相当于 echo 1+1
# 计算器例子
# 第一段负责输入式子
# 第二段转换成既定格式
# 第三段负责计算
# -I_ 指定参数替换到 _ 上
echo "4*5.7^3*sqrt(3+69%10)/3.14" | xargs -I_ echo "scale=2;_" | bc
find /usr/bin -name "ls*" -exec realpath {} \; | xargs -n1 du | cut -f 1 | sort -nr | xargs | { read line; echo $line; }
命令的输出可以多行,如果对一行的一部分处理(即所谓的列)。
命令是一股脑输出所有的结果的。因此接受该输入的命令要能处理多行。并不是所有命令都能支持多行的。比如 ls 只能接受单行的参数。
那么就必须使用:"多行 --> 多次调用单行命令" 的模式。
还是老朋友 xargs。
有一些命令,它可以针对每一行额外调用一个命令,如 find -exec ls -hdl {} \;
但是最有效的还是借助两个强大的命令:
# 多行 -> 多次单行
ls -l | xargs -i echo {}
# 单行 -> 多次分列
# sed 一次读取一行,然后分别对该行执行命令
# -n 只显示匹配行
# -r 正则
# [[:blank:]] 空白字符集
# [^[:blank:]] 非空白字符集
# () 分组,\1 引用第一个分组匹配的结果
# 1 命令只对第一行有效
# s/a/b/g ,s 字符串替换命令
# 其中:a 匹配部分,b 替换部分, g 全局标记,表示多次替换(空为一次)
# p 打印
# ; 命令分组分隔符,不同组独立重新执行。没分隔符时是继续执行
# 1{a;b} a;b 都只对第一行有效
# 整体就是以空格为分隔符,将多列转化为多行
ls -l | sed -nr '1s/([^[:blank:]]+)([[:blank]]+)/\1\n/gp' | xargs -i echo {}
# 多行 -> 多次单行 -> 多次分列
ls -l | awk '{if(NR==1) ; else {if(NR==3) for(i=1;i<=NF;i++) print $i; else for(i=1;i<=NF-1;i++)print $i}}' | xargs -i echo {}
这是 alpine linux 中默认使用的 shell,这个 shell 符合 posix 标准,移植性比较好,但功能稍微弱一些。
break [n]
:跳出 n 个循环continue [n]
:跳到 n 个循环的下一步case words in pattern) list;; esac
:分支语句[abc]
: 字符集,即 a 或 b 或 creturn [status]
: 返回值<、>、>>、<&0、>&1
`list`
// bash
[[ "$a" == "$b" ]]
// ash
[ "$a" == "$b" ]
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。