想把Linux玩溜,怎可缺少Shell这个技能点呢。把它归在计算机语言里吧,毕竟也是种脚本语言(虽然它也被看做是一个应用程序)
需注意点
1、在Shell中数字0和1并不代表真假
2、在Linux命令中-o这类表示选项,选项后紧跟着选项的参数。并且,不带选项的参数可以写在一起
脚本调试方法
sh/bash -x [脚本名称]
常用命令
read variable:
等待用户输入,并将输入存放于variable中,回车为输入结束
seq 结束值:
执行该语句,将以默认步长,从1开始递增到结束值
grep:
- -v反向过滤,表示过滤除xxx以外的内容
- -q过滤后非空的话则返回0,否则返回1
- -o部分匹配,并截取,输出,匹配-o之后的内容,但只要包含了这些内容就会被匹配,最后再截取这部分内容,输出到终端页面
- -i表示不区分大小写匹配
- -E表示或匹配,grep -E “error|drop”,匹配含有error或drop字符串的行,并输出
- “\<>“完全匹配,grep “\
“,表示需要匹配到xxx,xxx前后可以是空格或者特殊字符 - 正则表达式(https://man.linuxde.net/docs/shell_regex.html):grep “^eth[0-9]+$”,表示匹配eth开头并且以数字结尾的字符串。注意,如果加了-E参数,则grep表示使用扩展正则表达式(之前的匹配语句,将等价于^eth[0-9]+$),
awk:
- -F以某字符或符号进行分割,常见用法awk -F “2” ‘{print $1}’,表示用字符2进行分割,并输出第一部分
cut:
- -d “x” -f 1表示以字符x作为分隔符,并输出第一部分
解释器:
.sh文件开头一般都会有下面这句
#! /bin/bash:
目的是为了告诉系统,该脚本是使用了bash这一个解释器(还存在多种解释器,但一般bash最常用)
注释:
单行注释:在行开头使用’#’字符,进行单行注释
多行注释(块注释):
:<<BLOCK
被注释的内容...
BLOCK
echo命令:
用于向窗口输出文本
echo "lz"
若文本中需要使用转义字符,echo需要加-e参数
echo -e "lz\nlz"
变量:
- 变量前不加‘$’字符
定义一个变量的时候就应该赋初值,且变量与等号之前不可以有空格
my_age=24
读取变量时,需要在变量名前加入’$’符号(写入时不要要加),并且最好在变量左右两边,加入’{}’符号,以便解释器识别变量边界
echo "my age is ${my_age}"
只读变量可以使用readonly进行申明
#只读变量,被申明后,将不可以有再次被赋值操作,否则运行时会报错
my_age=24
readonly my_age
可以使用unset关键字删除变量,变量被删除后,将不能继续使用,且unset关键字不能删除只读变量
unset my_age
运行shell时,会同时存在3种变量:局部变量(程序员在shell脚本中,定义的仅在当前脚本中有效的变量)、环境变量(运行shell所需要的系统配置的环境变量,必要时也可以在shell脚本中进行定义)、shell变量(由shell脚本自身设置的特殊变量,shell变量中,有一部分是环境变量,有一部分是局部变量)
字符串:
单引号:
1、在单引号中的任何字符都会原样输出,所以在单引号中读取/写入变量是无效的
2、在单引号中嵌套单引号,若里层单引号中的内容含有变量,该变量会被读取。
lz='yyy'
echo '12'35${lz}4'12'
>>1235yyy412
双引号:
1、双引号中可以读取变量
2、双引号中可以存在转义字符name='lz'
namestr1="hello,${name}!"
namestr2="hello,"${name}"!"
namestr3="hello,"$name"!"
echo $namestr1 $namestr2 $namestr3
>>hello,lz! hello,lz! hello,lz!
获取字符串长度:
可以使用‘#’字符来获取字符串长度
lz='yyy' echo ${#lz} >>3
提取子字符串:
stringstr="hello lz"
echo ${string:6:8}
>>lz
查找字符的位置:
stringstr="hello lz"
#下句是反引号而不是单引号
echo `expr index "${stringstr}" l`\
重定向:
>:输出重定向
#输出1到ip_forward文件中,如果ip_forward文件存在则清空该文件,再更新内容。若不存在,则创建再更新内容
echo 1 > /proc/sys/net/ipv4/ip_forward
>>:输出追加重定向
#输出"lz"到test.txt文件夹,若test.txt文件存在,则在最后另起一行,追加输入字符串"lz"。若文件不存在,则创建再更新内容
echo lz >> /home/lz/test.txt
#重定向,并且不在末尾添加换行符
echo -n lz > /home/lz/test.txt
echo -n lz >> /home/lz/test.txt
数组
- 定义数组
#数组名=(值1 值2....)
arrayone=(1 2 3 'lz')
#echo ${arrayone[@]}可以按角标递增顺序输出数组内的所有元素
echo ${arrayone[@]}
>>1 2 3 lz
#或者
arrayone=(
1
2
3
'lz'
)
#也可以单独定义数组中的元素,且可以不按角标增长顺序
arrayone[0]=1
arrayone[3]='lz'
echo ${arrayone[@]}
>>1 lz
获取数组长度
lz=(1 2 3) echo ${#lz[@]} >>3
数组合并
l1=(1 2 3)
l2=(4 5 6)
l3=(${l1[@]} ${l2[@]})
或
l1+=(${l2[@]})
从命令行向shell脚本传递参数
还有些常用参数:
$$:脚本运行的当前进程ID号
$!:后台运行的最后一个进程的ID号
$-:显示Shell使用的当前选项,与set命令功能相同(没理解)
$?:显示最后命令的退出状态,0表示没有错误,其他任何值表名有错误,例:
#若编译成功则提示"compile successfully"
echo gcc -o hello hello.c
if [ $? -eq 0 ]
then
echo "compile successfully"
else
echo "compile fail"
fi
运算符
其他运算符和其他语言一样
需要注意的地方:
1、乘号(*)前需要加反斜杠(\)才能作为乘号使用
2、mac中的shell的expr语法是$((表达式)),此处表达式中的 ““ 不需要转义符号 “\”
`lz=expr $((23))`echo ${lz}
>>6
关系运算符(重要)
-eq:检测两数是否相等,相等返回true,与shell运算符中的==号一样
-ne:检测两数是否不等,不等返回true,与shell运算符中的!=号一样
-gt:检测左边的数是否大于右边的,如果是,返回true
-lt:检测左边的数是否小于右边的,如果是,返回true
-ge:检测左边的数是否大于等于右边的,如果是,返回true
-le:检测左边的数是否小于等于右边的,如果是,返回true
例:
val=${1} +${2}
#和是否等于3
if [ ${val} -eq 3 ]
then
echo "${1} + ${2} == 3"
else
echo "${1} + ${2} != 3, == ${val}"
布尔运算符
!:非运算,表达式为true则返回false,否则返回true
-o:或运算,有一个表达式为true则返回true
-a:与运算,两个表达式都为true才返回true
例:
if [ 1 -gt 0 -o 0 -gt 1 ]
then
echo "true"
else
echo "false"
if
>>true
逻辑运算符
&&:逻辑与
||:逻辑或
逻辑运算符和上面的布尔运算符的-o和-a的用法相同
字符串运算符(重要)
=或==:检测两个字符串是否相等,相等返回true
!=:检测两个字符串是否不等,不相等返回true
-z:检测字符串长度是否为0,为0返回true
-n:检测字符串长度是否为0,不为0返回true
${a}:搭配if语句可以检测字符串a是否为空,不为空,返回true
例:
str1=""
if [ ${str1} ]
then
echo "字符串不为空"
else
echo "字符串为空"
fi
if [ -z ${str1} ]
then
echo "字符串长度为0"
else
echo "字符串长度不为0"
fi
>>字符串为空
>>字符串长度为0
文件测试运算符(重要)
-b:检测文件是否是块设备文件,如果是,则返回true
-c:检测文件是否是字符设备文件,如果是,则返回true
-d:检测文件是否是目录,如果是,则返回true
-p:检测文件是否是有名管道,如果是,则返回true
-f:检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回true
-g:检测文件是否设置了SGID位,如果是,则返回true
-k:检测文件是否设置了粘着位(Sticky Bit),如果是,则返回true
-u:检测文件是否设置了SUID位,如果是,则返回true
-r:检测文件是否可读,如果是,则返回true
-w:检测文件是否可写,如果是,则返回true
-x:检测文件是否可执行,如果是,则返回true
-s:检测文件是否为空(文件大小是否大于0),不为空,则返回true
-e:检测文件(包括目录)是否存在,如果是,则返回true
例:
filedir="/home"
if [ -d ${filedir} ]
then
echo "这是目录"
else
echo "这不是目录"
fi
>>这是目录
echo(该命令会自动添加换行符)(可以理解为只是用来输出字符串的,不需要输出时,可以不使用此命令,当printf一样用就好)
1、-e:开启转义,例:
echo -e "hello world \r\nlz"
>>hello world
>>lz
2、原样输出字符串,不进行转义或取变量,使用单引号:
name="lz"
#注意,是单引号
echo '${name}\r\n'
>>${name}\r\n
echo -e '${name}\r\nlz'#开启转义
>>${name}
>>lz
3、显示结果定向至文件:
#在home路径下,重写test.txt文件
echo "hello world" > /home/test.txt
4、显示命令执行结果
#注意,是反引号,而不是单引号
echo `date`
>>输出当前时间和日期
#将时间和日期追加输出至date.txt文件中
echo `date` >> /home/date.txt
printf(不会自带换行符)
Shell中的printf和C中的差不多,跟echo的差别也就是,printf主要用在格式化输出,例:
#%-10s表示左对齐10个字符,不足10个字符,自动补空格,超过10个字符,则全显示
printf "%-10s %-8s %-4s\r\n" 姓名 性别 年龄
printf "%-10s %-8s %-4d\r\n" 李人 男 24
printf "%-10s %-8s %-4d\r\n" 李人人 男 24
printf "%-10s %-8s %-4d\r\n" 李人人人 男 24
test
test命令,常和if搭配使用,一般使用正常的if语句即可,可以不关注test
流程控制
1、if else
#将then和fi看做if语句的花括号({})即可
if condition
then
command
else
command
fi
#注意,最后只要一个fi即可
if condition
then
command
elif condition
then
command
else
command
fi
# 简化版,[]中的表达式为真时,输出exec sueecss
[ $? -eq 0 ] && echo "exec success"
# 多条件判断,如果lz等于yyy并且lz1等于xxx,则输出ok
if [[ x"${lz}" == x"yyy" ]] && [[ x"${lz1}" == x"xxx" ]]; then
echo "ok"
fi
2、for循环
想循环一定次数,可以使用关键字seq实现,例:
#seq,默认从1开始,且步长为1,seq 3 4 100 从3开始,步长为4,到100
for var in `seq 100`
do
command
done
#var依次为item1、item2...itemN,按此进行循环
for var in item1 item2 ...itemN
do
command
done
#var依次为str的下角标所对应的元素,按此进行循环
for var in str
do
command
done
#对执行该脚本时输入的参数进行遍历
for var in $*
do
command
done
#无线循环
for (( ; ; ))
#结合数组,遍历数组
lz=('f' 'y' 'i')
for element in ${lz[@]}; do echo "${element}"; done
>>f
>>y
>>i
3、while循环
while ((condition))
do
command
done
#例
var=89
while ((var < 100))
do
echo -e "${var} \c"
let var++
done
#无限循环
while :#或 while true
do
command
done
4、until循环:
until循环执行一系列命令,直到条件为true时为止,写法:
until condition
do
command
done
5、case语句:
case语句为多选择语句,可以用case语句进行一个值匹配,写法:
case 值 in
1模式/值)
command
...
;;
2模式/值)
command
...
;;
*)
command
;;
...
esac
在Shell的case中,取值可以为变量或常数,若匹配上某模式或值时,将执行该段语句,到;;结束。并且匹配后,不会再继续向下执行(和C不同)。若无任何一值匹配时,则可使用星号通配(相当于default)
6、break,continue语句,在Shell中同样适用,用法与C相同
函数
在Shell中函数的例子(带参数):
functest()
{
echo "run functest function"
echo "the first param is $1"
}
functest 1 2 3
输入/出的重定向(重要)
因为Shell主要是用来写脚本的,操作一些文件啥的,所以重定向还是很重要的
command > file:将输出重定向到file
command < file:将输入重定向到file
command >> file: 将输出以追加方式重定向到file
下面这几个目前还不是很理解
n > file:将文件描述符为n的文件重定向到file
n >> file:将文件描述符为n的文件以追加方式,重定向到file
n >& m:将输出文件m和n合并
n <& m:将输入文件m和n合并
<<tag:将开始标记tag和结束标记tag之间的内容作为输入
注意:一般情况下,每个linux命令运行时,都会打开三个文件:
标准输入文件(stdin),文件描述符为0,默认从stdin读取数据
标准输出文件(stdout),文件描述符为1,默认向stdout输出数据
标准错误文件(stderr),文件描述符为2,Shell脚本报错,会向stderr流中写入错误信息
其中stdout和stderr默认都是向屏幕输出的
默认情况下,command > file会将stdout重定向到file
默认情况下,command < file会将stdin重定向到file
如果希望stderr重定向到file,则:
command 2 > file
#追加
command 2 >> file
如果希望将stdout和stderr合并后重定向到file,则:
command > file 2>&1
如果希望对stdin和stdout都重定向,则:
#将command命令的输入stdin重定向到file1,输出stdout重定向到file2
command < file1 >file2
Here Document(重要)
Here Document是Shell中的一种特殊的重定向方式,用来将输入重定向到一个交互式Shell脚本或程序,写法:
#将两个delimiter中的内容(document)作为命令的输入传递给command
#注意这里是命令的输入,也就是说,这个命令要求输入,才有用,并不是指紧跟在命令后的输入参数
command << delimiter
document
delimiter
#例
mkdir << EOF
testdir
EOF
/dev/null
如果希望执行某命令后,不在屏幕或文件中进行输出,则可以将command的输出重定向到/dev/null下,写法:
command > /dev/null
#屏蔽标准输出和错误输出
command > /dev/null 2>&1
包含Shell文件
主要是指,可以将Shell文件进行相互应用,例:
#在1.sh中(可以不升级成执行权限)
name="lz"
#在2.sh中
#包含1.sh文件,注意./1.sh是path路径
. ./1.sh
echo "my name is ${name}"
>>my name is lz
交互脚本
如果需要对某个应用程序进行一些交互操作,这里提供几种方式
1、利用重定向,将输入重定向至某个文件或某块文档,但不能在交互的时候使用条件语句和循环语句
2、使用“|”管道,缺点和上面相同
3、使用expect库
此处推荐使用expect库,在ubuntu下直接apt-get install expect即可,以下例子为使用expect库对ftp应用程序进行交互操作(./ftp_put.sh)
#! /usr/bin/expect
set openflag 1
set drthost 192.168.175.1
set localfile ./1.iso
set fcount 0
#使expect语句永久等待条件成立
set timeout -1
spawn ftp -n
send "open ${drthost}\r"
send "user anonymous xxx@126.com\r"
while { ${openflag} } {
send "put ${localfile}\r"
expect "*Transfer complete*"
set fcount [expr ${fcount} + 1]
send_user "put file ok,${fcount}\r\n"
sleep 1
}
send "close\r"
send "bye\r"
interact
注意,如果需要在shell中嵌入expect可以单独写一个expect脚本,再在shell中调用即可,类似如下:
#! /bin/bash
date
./ftp_put.sh