「Script」Shell脚本基础一

一.Shell说明

Shell是一种用C语言编写的程序,提供了用户与内核进行交互操作的一种接口,它接受用户输入的命令,并把它送入内核去执行。内核是Linux系统的心脏,从开机自检时就驻留在计算机的内存中,直到计算机关闭为止。Shell独立于内核,它是连接内核与应用的桥梁。 同时Shell是一种命令语言,也是一种程序设计语言。

通常所说的Shell都是指Shell脚本,但是shell和shell script是两个不同的概念。
Shell编程只需要一个文本编辑器和一个能解释执行的脚本解析器就可以了。

二.常见的的Shell

  • Bourne Shell (/usr/bin/sh 或 /bin/sh)
  • Bourne Again Shell (/bin/bash)
  • C Shell (/usr/bin/csh)
  • K Shell (/usr/bin/ksh)
  • Shell for Root (/sbin/sh)

三.基本使用方法

  1. 可以通过文本编辑器创建文本并保存为.sh后缀文件。(sh代表shell)。 这里我们通过命令的方法来创建一个shell文件。在桌面新建一个名字为Shell的文件夹
#注释:执行cd 命令到文件夹目录
cd Desktop/Shell

#注释:通过touch命令创建shell文件
touch hello.sh

#注释:设置脚本解析器,使用#! 标记,#!标记告诉系统用什么脚本解析器来执行,也就是说用哪种shell
#! /bin/bash

这样一个后缀为sh的shell空文件就创建好了。下面我们可以通过文本编辑器打开我们创建的hello.sh文件,也可以通过vim命令打开。

  1. 添加脚本,脚本如下:
#注释:echo 用于向窗口输出文本
echo "hello world"

保存并退出。然后通过如下命令来查看文件权限

#注释:ls -l,命令以长格式的形式查看当前目录下所有可见文件的详细属性
ls -l hello.sh

使用上述命令后输出如下:
-rw-r--r-- 1 root staff 0 4 28 15:03 hello.sh
看下图

ls-l命令输出说明.png

通过上述输出结果可以看出我们要执行hello.sh文件,但是并没有执行的权限,所以通过下面的命令添加执行权限并执行脚本:

#注释 chmod +x 添加文件的执行权限
chmod +x ./hello.sh

#注释 执行文件
./hello.sh

正确操作就可以看到hello world的输出结果。

PS:操作文件时,一定要写成./hello.sh,因为直接写成hello.sh,系统会去指定目录寻找hello.sh文件,而我们并没有设置环境变量指定文件路径,使用./就是在当前目录寻找,因为我们已经cd到了当前目录。

四.注释

shell脚本中只有单行注释,即用#符号表示注释,如需要多行注释,只需要在每行前加#即可。

# 这是一段注释
# 这里用来说明输出hello world
echo "hello world"

五.变量

  1. 变量定义格式如下
#注释 变量名=值
var_name="定义的变量"

PS:定义时,等号前后都不能有空格,变量名定义规则如下:

  • 命名只能使用英文字母、数字和下划线组成,且首字符不能以数字开头
  • 中间不能有空格
  • 不能使用标点符号
    不能使用bash关键字
  1. 只读变量
name="readonly"
readonly only

#注释 此时如果修改name的值会报错
#name="write"
  1. 使用变量

在变量名前加$符号即可。也可以在变量名外加上花括号,也可不加。加上花括号是为了帮助编译器识别变量的边界。

var_name="这是一个变量"
echo $var_name
echo ${var_name}
  1. 删除变量

使用unset删除变量,变量删除后不可再使用(属于未初始化状态),无法使用unset删除只读变量。

var_name="var"
unset var_name
echo $varname
  1. 变量类型
  • 本地变量
    本地变量仅可以在用户当前Shell生命期的脚本中使用的变量,随着Shell的进程的消亡而无效。类似于C、C++、Java等语言中的局部变量。

  • 环境变量
    适用于所有由登录进程所产生的子进程,环境变量在用户登录后到注销之前的所有地方都有效。

  • 特殊变量(位置变量)
    用于向Shell脚本传递参数,是只读的。

  1. 字符串

字符串可以使用单引号,也可以使用双引号,也可以不同引号。

  • 单引号

单引号内的内容都会原样输出,不做任何处理,单引号中使用变量是无效的。而且单引号中不能出现单引号,使用转义字符也是无效的。

#注释:如果使用如下会报错
#注释:string='a string ' ' 或 string='a string \' '
#注释:如下使用不会做任何处理,直接原样输出,name为定义好的变量,例如:name='zhangsan'
#注释:string='a string $name'
string='a string'
echo $string
  • 双引号

双引号中可以使用变量,并且会做处理,也可以出现转义字符。

newName="new name"
newString="a new string ' \n $newName"
echo $newString
  • 字符串拼接
userName="张三"
userAge="20"
userSex="男"
#userInfo="姓名:$userName 年龄:$userAge 性别:$userSex"
#注释:也可以给变量加上双引号,输出结果是一样的
userInfo="姓名:"$userName" 年龄:"$userAge" 性别:"$userSex""
echo $userInfo
  • 获取字符串长度
#注释:格式:${#变量}
lengthString="a length string"
echo ${#lengthString}

#注释:也可以使用下面的代码
lengthString="a length string"
length=${#lengthString}
echo $length

PS:此处花括号不可省略。

  • 提取子字符串
#注释:格式:${字符串:起始位置:长度},起始位置下标从0开始
extractString="this is a long string"
extractNewString=${extractString:1:10}
echo $extractNewString
echo ${extractString:0:4}

也可以向下面这样操作:

extractString="this is a long string"
sourceStringLength=${#extractString}
#注释:sourceStringLength=${#extractString}-1
sourceStringLength=$sourceStringLength-1
resultString=${extractString:0:sourceStringLength}
#注释:resultString=${extractString:0:sourceStringLength-1}
echo $resultString

也可是使用一个参数

#注释:格式:${字符串:位置参数},标示从位置参数开始截取到最后
extractString="this is a long string"
echo ${extractString:5}
  • 查找子字符串
  • 删除字符串

字符串删除指定子字符串,是从左到右或从右到左的匹配规则,如果一开始匹配不到则删除失败。#标示从左到右的匹配,##,从右到左的匹配;%从右到左的匹配,%%从左到右的匹配。

#注释:格式:${字符串#要删除的子字符串}
removeString="this is a string"
resultRemoveString=${removeString#this}
echo $resultRemoveString

但是如果删除的子字符串不是从左第一位就开始能匹配的上则删除失败

removeString="this is a string"
resultRemoveString=${removeString#a}
echo $resultRemoveString

删除指定字符(或字符串)前所有的字符(包括指定字符),依然遵从从左到右的匹配规则,并且字符串中存在多个同样的字符时,是删除匹配到的第一个字符或字符串前的所有字符。

#注释:格式:${字符串#*要删除的子字符串}  也可以把a换成string,就是删除string本身和以前的所有字符
removeString="this is a string and new string"
resultRemoveString=${removeString#*a}
echo $resultRemoveString

从右侧匹配的例子,匹配成功后依然是删除左侧所有的字符

#注释:格式:${字符串##*要删除的子字符串}
removeString="this is a string and new string"
resultRemoveString=${removeString##*a}
echo $resultRemoveString

以上无论匹配规则如何,匹配成功后都是删除左侧的字符串,那么要删除右侧的字符串如何处理呢?看如下例子:

#注释:格式:${字符串%要删除的子字符串}
removeString="this is a string and new string a"
resultRemoveString=${removeString%a}
echo $resultRemoveString

这样会删除最右侧的a,输出this is a string and new string

删除指定范围的字符串都是一样的处理,如下代码:

#注释:格式:${字符串%要删除的子字符串*}
removeString="this is a string and new string"
#删除and以及右侧的全部字符串
resultRemoveString=${removeString%and*}
echo $resultRemoveString

下面是从左侧开始匹配,匹配到第一次匹配成功的以后删除右侧所有字符

#注释:格式:${字符串%%要删除的子字符串*}
removeString="this is a string and new string"
resultRemoveString=${removeString%%a*}
echo $resultRemoveString
  • 字符串替换
替换

六.echo 指令

echo 用来做字符串的输出。

  1. 输出普通字符串
echo "this is a string"

#此处的双引号可以省略
echo this is a string
  1. 使用转义字符
echo "\"this is a string\""
  1. 输出变量
name="变量"
echo $name
echo ${name}
  1. 显示换行与不换行

换行 \n,使用换行转义字符的时候在有些shell环境下是可以直接执行的,但是在bash环境下不能直接直接,首先可以使用```echo $SHELL命令查看当前用户正在使用的shell,如下:

ehco $SHELL

#注释:输入如下:
/bin/bash

这个时候如果定义如下输出可以看出效果:

newString="this is a new string and \n other"
echo $newString

#注释:输出结果如下:
this is a new string and \n other

我们发现换行符并没有起到作用,此时我们要使用-e来开启转义字符的功能,用如下格式:

newString="this is a new string and \n other"
#注释:这样就可以正常换行输出了
echo -e $newString

不换行输出:

newString="this is a new string"
addString="and string"
echo $newString
echo $addString

当我们按照如上格式输出结果时,会分两行输出,但是当我们想让他们在一行输出时需要如下格式:

newString="this is a new string \c"
addString="and string"
echo -e $newString
echo $addString

#注释:/c 不换行
  1. 显示执行命令的结果
#打印当前时间,符号为反引号`
echo `date`
  1. 结果重定向至文件
newString="this is a new string"
echo $newString > newfile

#注释:这样就将newString的内容写入了newfile文件

七.数组

数组中可以存放多个值,Bash Shell只支持一维数组,各元素之间用“空格” 分隔,初始化时也不需要定义数组大小,下标从0开始,如下:

  1. 数组定义
#注释:格式:变量名=("参数1" "参数2" "参数3" ...)
names=("张三" "李四" "王二麻子")
#注释:这样会输出数组元素的第0个,即参数1
echo $names

也可以通过如下格式为数组元素赋值:

names=()
names[0]="张三"
names[1]="李四"
names[2]="王二麻子"
echo ${names[1]} ${names[2]}
#注释:此时的花括号不可以省略
  1. 获取数组所有元素

使用@*可以获取数组的所有元素,如下:

names=()
names[0]="张三"
names[1]="李四"
names[2]="王二麻子"
echo ${names[1]} ${names[2]}
#输出所有数组元素
echo ${names[@]}
echo ${names[*]}

PS:使用@*获取的区别在于,使用@获取的结果是以单个元素的形式输出,使用*获取的结果是将结果组合成字符串的形式一并输出。

  1. 获取数组或单个元素长度
names=()
names[0]="张三"
names[1]="李四"
names[2]="王二麻子"
echo ${names[1]} ${names[2]}
#数组元素个数
echo ${#names[@]}
echo ${#names[*]}
#单个元素的字符长度
echo ${#names[2]}

八.运算符

原生bash不支持简单的数学运算,但是可以通过其他命令实现,awk或expr,expr常用。
expr是一款表达式计算工具。
运算操作使用反引号`

  1. 算数运算符
算数运算.png

以下所有运算中,运算符与表达式之间都要存在空格,没有空格是不对的。

正确:a + b

错误:a+b

#加法运算
c=`expr $a + $b`
echo $c

#减法运算
c=`expr $a - $b`
echo $c

#乘法运算,乘法运算时,需要加转义字符,使用\*
c=`expr $a \* $b`
echo $c

#除法运算
c=`expr $a / $b`
echo $c

#模运算
c=`expr $a % $b`
echo $c

#赋值运算
c=$a
echo $c

#是否相等,格式如下,必须将关键字then、else单独放在一行,并且[]与表达式之间也需要有空格,if...then...else...fi 后面会有说明。
if [ $a == $b ]
then
echo "a等于b"
else
echo "a不等于b"
fi
#是否不相等
if [ $a != $b ]
then
echo "a不等于b"
else
echo "a等于b"
fi
  1. 关系运算符
关系操作.png

只支持数字或值为数字的字符串

a=1
b=2

#检测两个数是否相等
if [ $a -eq $b ]
then
    echo "a等于b"
else
    echo "a不等于b"
fi

#检测两个数是否相等
if [ $a -ne $b ]
then
    echo "a不等于b"
else
    echo "a等于b"
fi

#检测左边的数是否大于右边的数
if [ $a -gt $b ]
then
    echo "a大于b"
else
    echo "a不大于b"
fi

#检测左边的数是否小于右边的数
if [ $a -lt $b ]
then
    echo "a小于b"
else
    echo "a不小于b"
fi

#检测左边的数是否大于等于右边的数
if [ $a -ge $b ]
then
    ehco "a大于等于b"
else
    echo "a不大于等于b"
fi

#检测左边的数是否小于等于右边的数
if [ $a -le $b ]
then
    echo "a小于等于b"
else
    echo "a不小于等于b"
fi
  1. 布尔运算符
布尔操作.png
a=1
b=2

#布尔 非运算
if [ $a != $b ]
then
    echo "a不等于b"
else
    echo "a等于b"
fi

#布尔 或运算
if [ $a == $b -o $a -le $b ]
then
    echo "成立"
else
    echo "不成立"
fi

#布尔 与运算
if [ $a != $b -a $a -le $b ]
then
    echo "成立"
else
    echo "不成立"
fi
  1. 逻辑运算符
逻辑操作.png

格式:[[ 表达式1 && 表达式2 ]][[ 表达式1 || 表达式2 ]]

a=1
b=2

#逻辑AND,&&两侧表达式同时成立为true,否则false
if [[ $a -le $b && $b -gt 1 ]]
then
    echo "成立"
else
    echo "不成立"
fi

#逻辑OR,||两侧表达式只要有一侧成立为true,否则为false
if [[ $a -eq $b || $b -gt 1 ]]
then
    echo "成立"
else
    echo "不成立"
fi
  1. 字符串运算符

PS:在做字符串操作的时候,在获取字符串值得过程中最好都加上双引号,如:"$string",避免出现一些问题。

字符串操作.png

string1="string ba"
string2="string ba"

#两个字符串是否相等
if [ "$string1" = "$string2" ]
then
    echo "两个字符串一样"
else
    echo "两个字符串不一样"
fi

#两个字符串是否相等
if [ "$string1" != "$string2" ]
then
    echo "两个字符串不一样"
else
    echo "两个字符串一样"
fi

#字符串长度是否为0
if [ -z "$string1" ]
then
    echo "字符串长度为0"
else
    echo "字符串长度不为0"
fi

#字符串长度是否为0
if [ -n "$string1" ]
then
    echo "字符串长度不为0"
else
    echo "字符串长度为0"
fi

#检测字符串是否为空
if [ "$string1" ]
then
    echo "字符串不为空"
else
    echo "字符串为空"
fi
  1. 文件测试运算符
文件操作.png

用于检测文件的各种属性。

filePath="/Users/helmsmanmac/Desktop/Shell.sh"

#注释:检测是否是块设备文件 -b
if [ -b $filePath ]
then
    echo "是块设备文件"
else
    echo "不是块设备文件"
fi

#注释:检测是否是字符设备文件
if [ -c $filePath ]
then
echo "是字符设备文件"
else
echo "不是字符设备文件"
fi

#注释:检测是否为目录
if [ -d $filePath ]
then
echo "是目录"
else
echo "不是目录"
fi

#注释:检测是否为普通文件
if [ -f $filePath ]
then
echo "是普通文件"
else
echo "不是普通文件"
fi

检测二个是都是一样的。

九.参数传递

在执行shell脚本时,可以向脚本内传递参数。在脚本内获取参数的格式为:${0}$0${1}$1${2}$2,以此类推...,$0是获取当前执行的文件命令的名称。如果我们执行./hello.sh,则获取的$0就是./hello.sh,也就是说命令本身就是第一个参数。

我们在脚本内添加以下脚本

echo ${0} ${1} ${2}
#echo $0 $1 $2

在终端输入如下命令即可:

#注释:注意参数之间的空格
./hello.sh hello1 hello2

以下是一些获取特殊字符处理参数:

#打印参数个数
echo ${#}

#打印参数列表
echo ${*}
echo ${@}

#打印脚本运行的当前进程ID
echo ${$}

#打印后台运行的最后一个进程的ID
echo ${!}

#打印Shell使用的当前选项
echo ${-}

#打印最后命令的退出状态,0表示没有错误,其他则为错误编号
echo ${?}

十.流程控制

  1. if
if [ $a -gt 1 ]
then
echo "a的值大于1"
fi

#注释:如果写成一行需要加分号分隔
if [ $a -gt 1 ]; then echo 1111; fi
  1. if...else
a=10

if [ $a -gt 1 ]
then
    echo "a的值大于1"
else
    echo "a的值不大于1"
fi
  1. if...else-if...else
string1=""
string2="string"

if [ $string1 = $string2 ]
then
    echo "string1等于string2"
elif [ $string1 ]
then
    echo "string的值不为空"
else
    echo "string1为空且string1不等于string2"
fi
  1. for

基本格式:

for value in {list}
do
    #执行的代码块(循环体)
done
#注释:依次打印1 2 3 4 5
for value in 1 2 3 4 5
do
    echo $value
done

如果列表数据比较多,可以使用略写方式,使用范围:

#注释:{}内不能有空格
for value in {1..20}
do
    echo $value
done

输出字符串也是一样的操作:

#注释:"one" "two" "thr" "fou" 也可以用引号包裹,也可不用,如果一个字符串中存在空格,则需要将整个字符串用引号包起来,否则会默认为由空格分割的多个字符串
for value in one two thr fou
do
    echo $value
done

打印指定路径下的所有文件:

#注释:打印当前目录下的所有文件,使用ls命令,
for value in $(ls)
do
    echo $value
done

也可以指定目录

#注释:指定目录,最后的*通配符,其他为省略的路径
filePath="/Users/****/Desktop/*"
for value in $filePath
do
    echo $value
done

输出参数

#注释:在终端输入 ./hello.sh 1 2 3 4,会打印出输入的参数
for value in $*
do
    echo $value
done

#注释:也可不带参数列表
for value
do
    echo $value
done

也可以写成类C风格的for循环

#注释:c语言格式,两个口号((之间不能有空格,))也不能有
for(( i = 0; i < 10; i++))
do
    echo $i
done

类C风格的写法可以省略参数,如下:

for((;;))
do
    echo "hello"
done

这样就形成了一个死循环,可以按control+c退出。

当然也可以同时对两个变量进行操作:

for((i = 0, j = 10; i <= 10; i++, j--))
do
    echo $i   $j
done
  1. while
a=0
while(($a<10))
do
    echo $a
#    a=`expr $a + 1`
    let a++
done

注释:let命令用于执行一个或多个表达式,变量计算中不需要加$。

while执行无限循环:

while :
do
    echo "hello"
done

#注释:或
while true
do
    echo "hello"
done
  1. until
a=0
until [ $a -gt 5 ]
do
    echo $a
    let a++
done

执行以上我们会发现,当until后的条件为假时才执行,所以until与while的执行条件正好是相反的。

  1. case
number=16
case $number in
1) echo "1" ;;
2) echo "2" ;;
3) echo "3" ;;
4) echo "4" ;;
5) echo "5" ;;
6) echo "6" ;;
7) echo "7" ;;
*) echo "没有对应的选项值" ;;
esac

*为通配符,当没有对应的选项时,则执行通配符选项。类似于其它语言的default。 esac 结束标记。

  1. break

跳出当前循环

for value in 1 2 3 4 5
do
    if [ $value -eq 3 ]
    then
        break
    fi

    echo $value

done

当value的值为3时,跳出循环,所以只能输出1 2

  1. continue

跳出当次循环

for value in 1 2 3 4 5
do
    if [ $value -eq 3 ]
    then
        continue
    fi

    echo $value

done

跳出值为3的当次循环。 输出 1 2 4 5

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,338评论 0 5
  • 第一个shell脚本 把上述内容输出到hello.sh中然后使用 bash hello.sh 或者 ./hell...
    熙熙爸爸阅读 439评论 0 0
  • .bat脚本基本命令语法 目录 批处理的常见命令(未列举的命令还比较多,请查阅帮助信息) 1、REM 和 :: 2...
    庆庆庆庆庆阅读 8,006评论 1 19
  • 一、shell脚本介绍 1.1 开头(环境使用shebang机制) #!/bin/bash 必须写在文件首行 符号...
    优果馥斯阅读 3,225评论 0 1
  • 这是我第一次尝试使用双拼输入法翻译一篇日本的Liunx基础教材上的一章。共花费一周左右。 shell的使用 前言 ...
    今後次阅读 1,041评论 0 4