Shell笔记

想把Linux玩溜,怎可缺少Shell这个技能点呢。把它归在计算机语言里吧,毕竟也是种脚本语言(虽然它也被看做是一个应用程序)

需注意点

1、在Shell中数字0和1并不代表真假
2、在Linux命令中-o这类表示选项,选项后紧跟着选项的参数。并且,不带选项的参数可以写在一起

常用命令

read variable:等待用户输入,并将输入存放于variable中,回车为输入结束
`seq 结束值`:执行该语句,将以默认步长,从1开始递增到结束值

解释器:

.sh文件开头一般都会有下面这句

#! /bin/bash:    

目的是为了告诉系统,该脚本是使用了bash这一个解释器(还存在多种解释器,但一般bash最常用)

注释:

单行注释:在行开头使用’#’字符,进行单行注释
多行注释(块注释):

:<<BLOCK    
被注释的内容...  
BLOCK  

echo命令:

用于向窗口输出文本
echo "lz"

变量:

  • 变量前不加‘$’字符
  • 定义一个变量的时候就应该赋初值,且变量与等号之前不可以有空格

    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、在单引号中的任何字符都会原样输出,所以在单引号中读取/写入变量是无效的
  • 双引号:
    1、双引号中可以读取变量
    2、双引号中可以存在转义字符

    name='lz'
    namestr1="hello,${name}!"
    namestr2="hello,"${name}"!"
    namestr3="hello,"$name"!"
    echo $namestr1 $namestr2 $namestr3
    >>hello,lz! hello,lz! hello,lz!

  • 获取字符串长度:
    可以使用‘#’字符来获取字符串长度

  • 提取子字符串:

    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  
  • 获取数组长度

从命令行向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语法是$((表达式)),此处表达式中的 “*” 不需要转义符号 “\”

关系运算符(重要)

-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

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 (( ; ; ))

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