文章目录
一、基本数据定义
1.1 变量
1、定义变量不加$
符号,使用变量要用$
;等号两边不能直接接空格符;通常大写字符为系统默认变量,自行设定变量可以使用小写字符。
2、双引号内的特殊字符如 $
等,可以保有其符号代表的特性,即可以有变量、转移字符;单引号内的特殊字符则只会原样输出。
NAME=yanzu;your_name="wu$NAME";echo $your_name # wuyanzu
NAME=tingfeng;your_name='xie$NAME';echo $your_name # xie$NAME
3、在一串指令的执行中,还需要藉由其他额外的指令所提供的信息时,可以使用反单引号 `指令` 或 $(指令)。
# uname -r 作为系统命令,并执行其内容
version=$(uname -r);echo $version
version=`uname -r`;echo $version
4、欲为扩增变量内容时,则可用 "$变量名称"
或 ${变量}
累加内容。
PATH="$PATH":/home/bin # 字符串就是这样拼接的
PATH=${PATH}:/home/bin
5、若该变量需要在其他子程序执行,则需要以 export 来使变量变成环境变量。
export PATH
env # 可查看环境变量
6、使用readonly可以将变量定义为只读变量,只读变量就是一个常量。unset 命令可以删除变量,变量被删除后不能再次使用,不能删除只读变量。
name="wuyanzu"
readonly name # 指定name为只读变量
age=13
unset age # 后面不能再使用age
7、预设变量,开始执行Script脚本时就会设定,且不能被修改。
$# #传递到脚本的参数的数量
$* #表示所有位置参数的内容,即以一个字符串显示所有向脚本传递的参数;$* 以"$1 $2 … $n"的形式输出所有参数
$@ #与$*相同,$@以"$1 $2 … $n"的形式输出所有参数
$$ #当前进程的进程号PID
$? #上个执行指令的回传值;用于检查上一条指令执行是否正确(linux执行返回的状态值:0表示没有错误,其他任何值表明有错误)
$! #后台运行的最后一个进程的进程号pid
8、如果不特别指明,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。
1.2 字符串
字符串可以用单引号,也可以用双引号,也可以不用引号。 字符串常用操作:拼接、获取长度、提取/替换子串、查找字符。
#!/bin/bash
your_name="wuyanzu"
# 单引号拼接
echo 'hello, '$your_name #hello, wuyanzu
echo 'hello, '${your_name} #hello, wuyanzu
# 双引号拼接,除单引号方式外加以下方式
echo "hello, $your_name" #hello, wuyanzu
echo "hello, ${your_name}" #hello, wuyanzu
# 获取长度
echo ${#your_name} #7
expr length $your_name
# 查找字符位置
expr index $your_name u #查找u第一次出现的位置,没有就是0,输出:2
expr index $your_name yu #查找y和u第一次出现的位置,谁先出现算谁,输出:2
# 提取子串
echo ${your_name:2} #从索引2开始,至结尾。输出:yanzu
echo ${your_name:2:3} #从索引2开始,取3个字符。输出:yan
expr substr $your_name 2 3 #从第二个字符开始,取3个。输出:uya
# 匹配删除子串
echo ${your_name#"wu"} #从头开始,匹配最短的wu,将其删除后。输出:yanzu
echo ${your_name##"wu"} #从头开始,匹配最长的wu,将其删除后。输出:yanzu
echo ${your_name%"wu"} #从尾开始,匹配最短的yanzu,将其删除后。输出:wu
echo ${your_name%%"wu"} #从头开始,匹配最长的yanzu,将其删除后。输出:wu
# 替换子串
echo ${your_name/u/o} #使用u替换从头开始第一个匹配的o,输出:woyanzu
echo ${your_name//u/o} #使用u替换匹配到的所有o,输出:woyanzo
1.3 数组
Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小,数组元素的下标由 0 开始。
Shell 数组用括号来表示,元素用"空格"符号分割开。
#!/bin/bash
# 定义一个数组
your_name=(w u yan "zu")
your_name[4]="!"
# 获取指定元素
echo ${your_name[2]} #获取第2个元素,输出:yan
# 获取数组长度
echo ${#your_name[*]} # 5
echo ${#your_name[*]}
# 获取所有元素
echo ${your_name[*]} # w u yan zu !
echo ${your_name[@]}
# 关联数组
declare -A name=(["my_name"]="wuyanzu" ["your_name"]="pengyuyan")
name["his_name"]="panchangjiang"
echo ${name["my_name"]} # wuyanzu
echo ${#name[*]} # 3
echo ${name[*]} # pengyuyan wuyanzu panchangjiang
二、基本运算符
原生bash不支持简单的数学运算,而是把运算符两边的数据(数值或者变量)当做字符串,把运算符本身也当做字符串,最终的结果是由一个运算表达式形成一个新的字符串。
但是可以通过其他命令来实现,例如和 (())
、$[]
、let
、expr
和 awk
。
#a=a+1
a=$((a+1))
a=$[a+1]
let a=a+1
a=$(expr $a + 1)
a=`expr $a + 1`
除了使用let
运算之外,其他运算符有以下两个要注意的点:
- 对于所有的运算符,表达式和运算符之间要有空格,例如
2+2
是不对的,必须写成2 + 2
。 - 条件表达式要放在方括号之间,并且要有空格,例如:
[$a == $b]
是错误的,必须写成[ $a == $b ]
。
let
命令对格式要求要比 expr
命令宽松,变量计算中不需要加上 $
来表示变量,且表达式和运算符之间不要空格也ok。
2.1 算数、字符串运算符
乘号*
前边必须加反斜杠\
才能实现乘法运算,如果运算表达式中使用()
运算符,也要加反斜杠\
,如a \* \( b + c \)
。
# 算数运算符
let c=a+b # c=a+b
let 'c= a +b' # c=a+b
let a++ # a=a+1
let a+=10 # a=a+10
#下列运算等价于:$(expr $a + $b)
`expr $a + $b` # a+b
`expr $a - $b` # a-b
`expr $a \* $b` # a*b
`expr $b / $a` # a/b
`expr $b % $a` # a%b
a=`expr $a + $b`# 把运算结果赋给a
a=$b # 把变量b的值赋给a
[ $a == $b ] # a是否等于b
[ $a != $b ] # a是否等于b
# 字符串运算符
[ $a = $b ] #检测两个字符串是否相等,相等返回 true
[ $a != $b ] #检测两个字符串是否不相等,不相等返回 true
[ -z $a ] #检测字符串长度是否为0,为0返回 true
[ -n "$a" ] #检测字符串长度是否不为 0,不为 0 返回 true
[ $a ] #检测字符串是否不为空,不为空返回 true
2.2 关系、布尔、逻辑运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
# 关系运算符
[ $a -eq $b ] # a == b
[ $a -ne $b ] # a != b
[ $a -gt $b ] # a > b
[ $a -lt $b ] # a < b
[ $a -ge $b ] # a >= b
[ $a -le $b ] # a <= b
# 布尔运算符
[ $a -lt 20 -a $b -gt 100 ] # -a:与
[ $a -lt 20 -o $b -gt 100 ] # -o:或
[ ! false ] # !:非
# 逻辑运算符
[[ $a -lt 100 && $b -gt 100 ]] # 逻辑AND
[[ $a -lt 100 || $b -gt 100 ]] # 逻辑OR
2.3 文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。
[ -b $file ] # 检测文件是否是块设备文件,如果是,则返回 true
[ -c $file ] # 检测文件是否是字符设备文件,如果是,则返回 true
[ -d $file ] # 检测文件是否是目录,如果是,则返回 true
[ -f $file ] # 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true
[ -g $file ] # 检测文件是否设置了 SGID 位,如果是,则返回 true
[ -k $file ] # 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true
[ -p $file ] # 检测文件是否是有名管道,如果是,则返回 true
[ -u $file ] # 检测文件是否设置了 SUID 位,如果是,则返回 true
[ -r $file ] # 检测文件是否可读,如果是,则返回 true
[ -w $file ] # 检测文件是否可写,如果是,则返回 true
[ -x $file ] # 检测文件是否可执行,如果是,则返回 true
[ -s $file ] # 检测文件是否为空(文件大小是否大于0),不为空返回 true
[ -e $file ] # 检测文件(包括目录)是否存在,如果是,则返回 true
2.4 test 命令
test
命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
具体的条件关系、布尔、逻辑、以及文件测试,可参考以上基本运算符。
cd /bin
if test -e ./notFile -o -e ./bash
then
echo -e "至少有一个文件存在!\n" # -e可输出转移字符
else
echo -e "两个文件都不存在!\n"
fi
三、控制流与函数
3.1 条件语句
知道if...elif...else
的写法,其他的就都会写了。
if condition1; then # 如果条件表达式与then连着写,要使用分号‘;’分割
command1
elif condition2 # 这里分号‘;’可写可不写
then
command2
else
commandN
fi
3.2 循环语句
在循环中也可以使用break
和continue
。
for var in item1 item2 ... itemN; do command1; command2… done;
while condition
do
command
done
until condition
do
command
done
3.3 分支语句
case
取值后面必须为单词 in
,每一模式必须以右括号结束。
取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;
。
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
esac
3.4 函数
定义函数function关键字可选,不带任何参数,所有函数在使用前必须定义。
函数的返回值只能是一个介于 0~255的值,在调用该函数后通过 $?
来获得,参考预设变量。
# 函数定义,可以不用function
function demo(){
echo "第一个参数为 $1"
echo "第二个参数为 $2"
echo "参数总数有 $# 个"
echo "作为一个字符串输出所有参数 $*"
return 8
}
# 函数调用
demo 1 2 3 4 5 6 7
echo "demo 返回值为 $?"
四、重导向与管线命令
4.1 数据流重导向(>、>>、<、<<)
标准输入(standard input)是命令的输入,默认指向键盘。数据流重导向可以将原本需要由键盘输入的数据,改由文件内容来取代。
- 标准输入 (stdin) :代码为 0 ,数据流重导向使用
<
或<<
; <
:将原本需要由键盘输入的数据,改由文件内容来取代。# 用 stdin 取代键盘的输入以建立新文件的简单流程,相当于copy cat > catfile < ~/.bashrc
<<
:代表的是结束的输入字符 的意思。# 用 stdin 取代键盘的输入以建立新文件的简单流程,相当于copy cat > catfile << "eof" > this is test > eof # 输入这关键词,立刻就结束而不需要输入 [ctrl]+d cat catfile this is test
标准输出(standard output)指的是指令执行所回传的正确的讯息,默认指向屏幕;标准错误输出(standard error output)可理解为指令执行失败后,所回传的错误讯息,默认指向屏幕。数据流重导向可以将输出分别传送到其他的文件或装置去。
- 标准输出 (stdout):代码为 1 ,数据流重导向使用
>
或>>
(1>
或1>>
); - 标准错误输出(stderr):代码为 2 ,数据流重导向使用
2>
或2>>
(没空格)。 >
:以覆盖的方法将数据输出到指定的文件或装置上(文件原有内容会被覆盖);>>
:以累加的方法将数据输出到指定的文件或装置上(在文件原有内容上输出);# 将 stdout 与 stderr 分存到不同的文件去 find /home -name .bashrc > list_right 2>> list_error
空设备文件/dev/null
可以理解为一个垃圾桶黑洞装置,它可以吃掉任何导向这个装置的信息。
# 将错误的数据丢弃,屏幕上显示正确的数据
find /home -name .bashrc 2> /dev/null
如果要将正确与错误数据通通写入同一个文件,这个时候就得要使用特殊的写法。两股数据同时写入一个文件,又没有使用特殊的语法, 此时两股数据可能会交叉写入该文件内,造成次序的错乱。
# 将指令的数据全部写入名为 log 的文件中
find /home -name .bashrc > log 2> log <==错误
find /home -name .bashrc > log 2>&1 <==正确,比较常见的一种写法
find /home -name .bashrc 2> log 1>&2 <==正确
find /home -name .bashrc &> log <==正确
【总结】为何要使用命令输出重导向呢?有以下几点:
- 屏幕输出的信息很重要,而且我们需要将他存下来的时候;
- 背景执行中的程序,不希望他干扰屏幕正常的输出结果时;
- 一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的执行结果,希望他可以存下来时;
- 一些执行命令的可能已知错误讯息时,想以『 2> /dev/null 』将他丢掉时;
- 错误讯息与正确讯息需要分别输出时。
4.2 命令执行的判断依据(;、&&、||)
cmd1; cmd2
若cmd1
执行的结果是否正确不影响cmd2
的执行,则cmd1执行完后就会立刻接着执行后面的cmd2
了。例如在关机的时候我希望可以先执行两次sync
同步化写入磁盘后才shutdown
计算机,则可以在指令之间加;
。
sync; sync; shutdown -h now
cmd1 && cmd2
若 cmd1
执行完毕且正确执行($?=0
),则开始执行 cmd2
;若 cmd1
执行完毕且为错误 ($?≠0
),则 cmd2
不执行。
#使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 建立 /tmp/abc/hehe
ls /tmp/abc 2> /dev/null && touch /tmp/abc/hehe
cmd1 || cmd2
若 cmd1
执行完毕且正确执行($?=0
),则 cmd2
不执行;若 cmd1
执行完毕且为错误 ($?≠0
),则开始执行 cmd2
。
# 测试 /tmp/abc 是否存在,若不存在则予以建立,若存在就不作任何事情
rm -r /tmp/abc;
ls /tmp/abc || mkdir /tmp/abc
一般来说,假设判断式有三个,常见写法就是:command1 && command2 || command3
。
# 注意两条指令之间的区别
ls /tmp/vbirding &> /dev/null && echo "exist" || echo "not exist"
not exist
ls /tmp/vbirding &> /dev/null || echo "not exist" && echo "exist"
not exist
exist
4.3 管线命令(|)
管线命令|
仅能处理经由前面一个指令传来的正确信息,也就是|前面指令的 stdout 信息,作为|
后面指令的stdin,对于 stderr 并没有直接处理的能力。
# /etc内容较多,不便查阅,可利用less的功能
ls -al /etc | less
在每个管线后面接的第一个数据必定是指令!而且这个指令必须要能够接受 stdin 的数据才行,这样的指令才可以是为管线命令,例如 less, more, head, tail, cut, grep, sort, wc, uniq
等都是可以接受 stdin 的管线命令。至于例如 ls, cp, mv
等就不是管线命令了!因为它们并不会接受来自 stdin 的数据。
uniq [-ic]
-i :忽略大小写字符的不同;
-c :进行计数
last | cut -d ' ' -f1 | sort | uniq -ci
wc [-lwm]
-l :仅列出行;
-w :仅列出多少字(英文单字);
-m :多少字符;
没有参数:列出行数、单词数、字符数
cat /etc/passwd | wc
如果你硬要让 stderr 可以被管线命令所使用,其实也可以处理,利用数据流重导向2>&1
,让 2>
变成 1>
。
# 双向重导向:tee,会同时将数据流分送到文件与屏幕(stdout)。
tee [-a] file
-a :以累加 (append) 的方式,将数据加入 file 当中!
# 让gcc编译报错的stderr,通过管道命令tee输出至文件log
gcc main.c -o main 2>&1 | tee log
4.4 字符转换命令(tr、join、paste)
命令tr
可以用来转换或删除文件中的字符!
tr [OPTION]... SET1 [SET2]
-d :删除讯息当中的 SET1 这个字符串
-s :缩减连续重复的字符成指定的单个字符
没有参数:用SET2替换SET1,不会缩减连续重复的字符
# 将 last 输出的讯息中,所有的小写变成大写字符
last | tr '[a-z]' '[A-Z]'
# 将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
cat /etc/passwd | tr -d ':'
命令join
将两个文件中,指定栏位内容相同的行连接起来。找出两个文件中,指定栏位内容相同的行,并加以合并,再输出到标准输出设备。
【注】需要特别注意的是,在使用 join 之前,你所需要处理的文件应该要事先经过排序 (sort) 处理!否则有些比对的项目会被略过。
join [-ti12] file1 file2
-a<1或2>:除了显示原来的输出内容之外,还显示指令文件中没有相同栏位的行
-t<字符>:使用栏位的分隔字符,默认是空格
-i :忽略大小写的差异;
-1<栏位>:file1中要被用来分析的栏位,如果没有指定文件的栏位,默认是该文件的第一栏
-2<栏位>:file2中要被用来分析的栏位
# 将文件a.csv join b.csv
cat a.csv
1,China
2,America
3,Russia
cat b.csv
1,China,Beijing
2,America,Washington
3,Russia
# 将a.csv的第一栏与b.csv的第二栏做对比,如果相等,则将其整合成一列,且将用来分析的相同栏移到最前面
join -1 1 -2 2 -t',' -a 1 a.csv b.csv
join -2 2 -t',' -a 1 a.csv b.csv # 默认a.csv的第一栏
1,China,Beijing
2,America,Washington
3,Russia
命令paste
就要比 join
简单多了!相对于join
必须要比对两个文件的数据相关性, paste
就直接将两行贴在一起,且中间以 tab 键隔开而已!
paste [-d] file1 file2
-d :后面可以接分隔字符,默认是tab键隔开
- :如果 file 部分写成 - ,表示来自 standard input
# 下面两条指令效果一样
paste -d "-" a.csv b.csv
cat a.csv | paste -d "-" - b.csv
1,China-Beijing,1
2,America-Washington,2
3,Russia-Mosco,4
4.5 xargs 与 - 的用途
xargs
可以读入 stdin 的数据,并且以空格符或断行字符作为分辨,将 stdin 的资料分隔成为 arguments 。 因为是以空格符作为分隔,所以,如果有一些档名或者是其他意义的名词内含有空格符的时候, xargs
可能就会误判了。
xargs [-0pnd] command
-0:如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等等字符时,这个 -0 参数可以将他还原成一般字符
-p:当每次执行一个argument的时候询问一次用户
-n num:表示命令在执行的时候一次用的argument的个数,默认是用所有的
-d delim:默认的xargs分隔符是回车,argument的分隔符是空格,这里修改的是xargs的分隔符
无参数:当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!
# 多行输入用echo单行输出
cat a.csv | xargs
1,China 2,America 3,Russia
# 每次执行用两个参数
cat a.csv | xargs -n2
1,China 2,America
3,Russia
# 用 rm 删除太多的文件时候,可能得到一个错误信息:/bin/rm Argument list too long. 用 xargs 去避免这个问题
find . -type f -name "*.log" | xargs rm -f
# 统计一个源代码目录中所有 php 文件的行数
find . -type f -name "*.php" | xargs wc -l
# 查找所有的 jpg 文件,并且压缩它们
find . -type f -name "*.jpg" | xargs tar -czvf images.tar.gz
在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的stdin的一部分内容。
# paste中用过的一个例子
cat a.csv | paste -d "-" - b.csv
# 将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是传送到 stdout;
# 经过管线后,将 tar -cvf - /home 传送给后面的 tar -xvf - 。
# 后面的这个 - 则是取用前一个指令的 stdout, 因此,我们就不需要使用 filename 了!
mkdir /tmp/homeback
tar -cvf - /home | tar -xvf - -C /tmp/homeback
五、shell中常用命令
5.1 程序出错,中断整个脚本
linux所有命令执行返回的状态值:0表示没有错误,其他任何值表明有错误。
如果只检擦某一条指令的返回状态是否正确,可以使用下面的方式。
if
[ $? -ne 0 ]
then
return $?
fi
如果要检查shell脚本中的每一条指令的返回 状态,则可以用下面的方式。
set -e # 程序中任意一条命令返回非0的值,终端shell程序
5.2 操作指定时间内更新过的文件
find
找到./delve
目录下10分钟内修改过的.go
文件,然后将这些文件通过scp
赋值到23机器的指定目录下面。find命令和scp命令之前总结过。
for file in $(find ./delve -mmin -10 -name "*.go")
do
dir=$(dirname $file)
dest=23:/home/huangqiqi/golang/delve_log${dir#"./delve"}
scp -r $file $dest
done