Shell编程快速入门

本文详细介绍了Shell脚本的基础知识,包括如何创建和运行脚本、基础语法、变量类型(局部、环境、shell变量)、字符串操作、数组、流程控制结构(if-else、for、while、until、case)、函数定义与调用、参数处理以及输入/输出重定向。此外,还讲解了文件测试运算和命令替换等实用技巧,适合初学者入门。
摘要由CSDN通过智能技术生成

参考指南:

本文主要部分参考如下:

https://www.w3cschool.cn/shellbook/uglqdozt.html

https://www.runoob.com/linux/linux-shell-variable.html

shell脚本的创建 & 运行:

保存文件为xxx.sh

运行方法:

  1. 直接赋予执行权限运行

    chmod +x /path/to/test.sh
    /path/to/test.sh
    
  2. 将文件作为参数传入解释器运行

    bash /path/to/test.sh
    
    # 或
    source /path/to/test.sh
    
    # 或
    . /path/to/test.sh
    

基础语法介绍:

以helloWorld程序为例:

#!/bin/bash -v
# test.sh
echo "Hello, World"

使用以上两种执行方式

  1. 直接执行:

    image-20210505175055026

  2. 参数执行:

    image-20210505175117337

这里主要看第一行的#!, 其只是系统使用之后的解释器与相应的参数来执行脚本文件

#在shell语法中是注释的作用, 直接执行的时候就不会注意这两行

变量:

变量类型:

  • 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  • 2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
  • 3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

定义与使用

定义变量, 不加$, 并且注意, 变量名与等号, 等号与后头的表达式间不能有空格

其次就是, 变量名遵循编程通用的变量名要求规则

使用变量就在变量名前加$, 可以选择加上{}用于帮助解释器识别变量的边界, 推荐加上{}

var="This is a var"
echo $var

image-20210505181408630

只读变量:

变量定义完成后, 加上个readonly, 就会将变量设置为只读, 之后无法修改

如果之后修改, 就会出现报警

var="This is a var"
readonly var
echo ${var}
var="New Value"

image-20210505181803279

删除变量:

unset variable_name

删除的变量无法再次使用

不能删除只读变量

var="This is a var"
varR="This is a readonly var"
readonly varR
echo ${var}
echo ${varR}
# var="New Value"
unset var
unset varR
echo ${var}
echo ${varR}

image-20210505182058722

字符串:

字符串作为Shell中特殊的也是最好用的变量类型

定义:

可以使用‘’ “” 或不用 不同的定义方式有不同的特点

由于变量名之前是需要加$的, 所以如果不加的话默认就为字符串

str='this is a string'

单引号定义特点

  1. 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
  2. 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用
your_name='runoob'
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str

双引号定义特点:

  1. 字符串可以内嵌变量
  2. 允许出现转义字符

所以, 通常默认使用“”, 如果需要raw string, 则使用‘’

字符串拼接:

your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting  $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2  $greeting_3

image-20210505183529695

可以看到字符串拼接的很自然, 直接使用连续配对的“”拼接即可

注意到一个细节:

echo同一行输出俩变量, 中间是有俩空格的, 但是输出结果只有一个空格, 经测试:

  • 在没有空格, 一个空格的情况, 都不会输出空格
  • 在两个空格以上的情况, 输出一个空格

获取字符串长度:

使用#

string="abcd"
echo ${#string} #输出 4

image-20210505184638754

获取子串:

string="runoob is a great site"
echo ${string:1:4} # 输出 unoo

image-20210505184717598

index从0开始

查找子串:

string="runoob is a great site"
echo `expr index "$string" io`  # 输出 4

注意, 这里使用到了`` `与 expr命令, 并不是字符串自带的功能, 主要使用到了expr的功能

直接参考Linux命令

其他功能:

其他字符串操作很多都是通过expr实现的, 直接去看后头的字符串操作部分

数组:

bash仅支持一维数组

栗子:

arr=(1 2 3 4)
echo ${arr[0]}
echo ${arr[@]}
echo ${arr[*]}
echo ${#arr[@]}
echo ${#arr[0]}

image-20210505195238731

定义:

array_name=(value0 value1 value2 value3)

# 或
array_name=(
value0
value1
value2
value3
)

#或
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

读取元素:

${数组名[下标]}

# 获取所有元素
echo ${array_name[@]}
echo ${array_name[*]}

获取数组长度:

# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

注释:

单行注释直接使用#标记即可

多行注释使用

:<<EOF
注释内容...
注释内容...
注释内容...
EOF

# 或者其他符号
:<<'
注释内容...
注释内容...
注释内容...
'

:<<!
注释内容...
注释内容...
注释内容...
!

命令替换:

参考博客:

https://www.cnblogs.com/chengd/p/7803664.html

命令替换与变量替换的作用相似, 变量替换是将变量的值替换到shell中, 而命令替换是将命令返回的结果替换到shell中

可以使用两种方法:

$() 或 ````

$()比较直观, 但是并非所有类unix系统都支持

反引号 所有系统都支持, 但并不直观

所以推荐使用第二种

echo的替代方案: printf

基本语法格式:

printf  format-string  [arguments...]
printf "%d is bigger than %d\n" 3 2

image-20210505224948178

使用上基本与C++相同, 易于上手

也可以直接在字符串中加参数

v1=1
v2=2
v3=3
printf "${v1} ${v2} ${v3}\n"

image-20210505230550732

获取输入 read:

参考博客;

https://blog.csdn.net/zhizhengguan/article/details/88391694

使用read获取用户输入

echo -n "Enter your name:" #echo -n不会在字符串末尾输出换行符
read name
echo "Hello $name"

image-20210507122215262

不指定变量的情况:

read不指定变量时,会存储到特殊环境变量REPLY中

read -p "Enter your name:" 
echo "Hello $REPLY"

image-20210507122341436

其他常用参数:

  1. 输出提示信息

    read -p str
    
    read -p "Enter your name:" name
    echo "Hello ${name}"
    

    image-20210507123045249

  2. 设定超时时间:

    read -t set
    

    如果未超时, 会返回0, 超时会返回一个随机非零值

    read -t 5 name
    echo "ans=$?"
    echo "Hello ${name}"
    

    image-20210507123233772

  3. 限制字符输入个数:

    当输入的字符达到预设字符个数时, 就自动退出

    read -n num
    
    read -n 5 name
    echo "ans=$?"
    echo "Hello ${name}"
    

    image-20210507160034473

  4. 输入密码

    read命令会将输入的文本颜色设成跟背景色一样, 达到不显示的目的

    read -n 5 name
    echo "ans=$?"
    echo "Hello ${name}"
    

    image-20210507160136519

流程控制:

和 Java、PHP 等语言不一样,sh 的流程控制不可为空

如果一个if-else的分支为空, 则不要写这个分支, 否则报错

后头所有的condition都是表达式, 使用双括号(())或中括号[]框起来的部分, 如

if [ $a = $b ]
then
   echo "$a = $b : a 等于 b"
else
   echo "$a = $b: a 不等于 b"
fi

if-else:

语法格式:

if condition
then
    command1 
    command2
    ...
    commandN 
fi

末尾的fiif倒过来写, 后头还有很多类似的语法习惯

if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi

级联版本

if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

for

语法格式

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

item1….itemN, 可以使用seq生成序列, 如:

for i in `seq 1 5`
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh 
i=1
i=2
i=3
i=4
i=5

更方便的使用序列的方法

[hhtxzzj@localhost temp]$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
for i in {1..10}
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh 
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10

仿C++格式:

语法与原生for有很大不同, 但是就是这样, 记着就对了

for((i=1;i<10;++i));
do
...
done

字符循环:

直接传入字符序列即可

list="rootfs usr data data2"
for i in ${list}
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh 
i=rootfs
i=usr
i=data
i=data2

for i in `ls`
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh 
i=makeFiles.sh
i=shellTest.sh

while

while condition
do
    command
done

例子:

int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done

image-20210507114006836

until

until 循环执行一系列命令直至条件为 true 时停止

其实就是while加个!

没必要的语法, 在其他语言中都没这东西, 了解一下就好

until condition
do
    command
done

case:

其实就是其他语言的switch-case

casein
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac
  1. 每个case最后以右括号结束

    可以为变量或常数, 一旦匹配, 将会一直执行直到遇到双分号;;

  2. ;;

    相当于C++中的break

break & continue

shell中也有break & continue

其作用也是跳出最内层循环

函数:

函数定义语法:

[ function ] funname [()]

{

    action;

    [return int;]

}

几个注意点:

  1. 函数名前头的function可选, 不加也能用

  2. 函数定义时不能指定参数, 即括号中必须为空, 参数调用后头再讲

  3. return也可选, 如果没有return, 则默认最后一条命令的运行结果作为返回值

  4. return后跟的数值n必须为0<=n<=255

    如果超过255会被舍入

栗子:

function fun(){
    echo "This is a funciton"
    return 666
}
fun
var=$?
echo "var=${var}"

image-20210507115746439

可以看到, 函数整体定义起来与C++基本相同

返回值:

Shell函数的返回值不能像C++一样, 以表达式的形式返回, 而是需要特殊的调用

在调用函数之后, 使用$?获得函数的返回值

function fun(){
    echo "This is a funciton"
    return 666
}
fun
var=$?
echo "var=${var}"

image-20210507115746439

参数:

之前说到函数定义时不能有参数, 但是调用的时候可以传入参数

function fun(){
    echo "1 var = $1"
    echo "2 var = $2"
    echo "3 var = $3"
    echo "4 var = $4"
    echo "5 var = $5"
}

fun 5 6 7 8 9

image-20210507121638986

函数中使用参数的方法与脚本中调用外部传入参数的方法相同:

参数处理说明
$#传递到脚本或函数的参数个数
$*以一个单字符串显示所有向脚本传递的参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-显示Shell使用的当前选项,与set命令功能相同。
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

获取传入脚本的参数:

使用以下保留变量获取传入的参数:

参数处理说明
$#传递到脚本的参数个数
$n​获取第n个参数, index从0开始
$*以一个单字符串显示所有向脚本传递的参数。 如"$*“用「”」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但*相当于将n个参数合成了一个, 如“1 2 3”, 而@等价与“1” “2” “3”
$-显示Shell使用的当前选项,与set命令功能相同。
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
echo "args 0 = ${0}"
echo "args 1 = ${1}"
echo "args 2 = ${2}"
echo "args 3 = ${3}"
echo "args 4 = ${4}"
echo "args # = ${#}"
echo "args * = ${*}"
echo "args @ = ${@}"
echo "args $ = ${$}"
echo "args ! = ${!}"
echo "args - = ${-}"
echo "args ? = ${?}"

image-20210505201344573

可以看到第一个参数是文件名

$*$@ 的区别:

echo "-- \$* 演示 ---"
for i in "$*"; do
    echo $i
done

echo "-- \$@ 演示 ---"
for i in "$@"; do
    echo $i
done

image-20210505213226276

数值运算:

bash原生并不支持数值运算, 但是可以通过其他命令实现, 如awk expr, expr最常用

运算符说明举例
+加法expr $a + $b 结果为 30。
-减法expr $a - $b 结果为 -10。
*乘法expr $a \* $b 结果为 200。
/除法expr $b / $a 结果为 2。
%取余expr $b % $a 结果为 0。
=赋值a=$b 将把变量 b 的值赋给 a。
==相等。用于比较两个数字,相同则返回 true。[ $a == $b ] 返回 false。
!=不相等。用于比较两个数字,不相同则返回 true。[ $a != $b ] 返回 true。

但是数值运算最好还是使用以下方式更为方便

$((表达式))
# 或
$[表达式]

直接输出:

echo $((1+1))
echo $((5%2))
echo $((5!=2))
echo $((5==2))
echo $[1+1]
echo $[5%2]
echo $[5!=2]
echo $[5==1]

image-20210505222549243

赋值运算:

var=$((5-1))
echo $var
var=$[55-1]
echo $var

image-20210505222952352

关系运算:

ans=$[5==1]
echo $ans
ans=$[5!=1]
echo $ans
ans=$[5>1]
echo $ans
ans=$[5>=1]
echo $ans
ans=$[5<1]
echo $ans
ans=$[5<=1]
echo $ans

image-20210505223129755

还有一种参数化的运算

参数化运算虽然没有$[]来的好用, 但是最好也需要掌握, 看代码的时候肯定会用到

image-20210505224436547

逻辑运算:

true=1
false=0

echo $[!true]
echo $[$true && $false]
echo $[$true || $false]

image-20210505223554909

image-20210505224448774

位运算:

var1=64
var2=32
var3=65
echo $[var1 | var2]
echo $[var1 & var3]
echo $[~var1]

image-20210505223841545

字符串运算:

运算符说明举例
=检测两个字符串是否相等,相等返回 true。[ $a = $b ] 返回 false。
!=检测两个字符串是否不相等,不相等返回 true。[ $a != $b ] 返回 true。
-z检测字符串长度是否为0,为0返回 true。[ -z $a ] 返回 false。
-n检测字符串长度是否不为 0,不为 0 返回 true。[ -n “$a” ] 返回 true。
$检测字符串是否为空,不为空返回 true。[ $a ] 返回 true。

这里注意

字符串运算并不能像表达式一样直接返回值, 即变量赋值或输出时不能直接使用字符串运算

只能用在上头的流程控制语句中, 如if-else

a="abc"
b="efg"

if [ $a = $b ]
then
   echo "$a = $b : a 等于 b"
else
   echo "$a = $b: a 不等于 b"
fi

echo "ans=$[$a = $a]"
echo "ans=$[$a = $b]"
printf "ans=%d\n" $[$a = $a]
printf "ans=%d\n" $[$a = $b]

image-20210507163901702

可以看到返回值都是0, 但是在if-else中却可以正常工作

文件测试运算:

操作符说明举例
-b file检测文件是否是块设备文件,如果是,则返回 true。[ -b $file ] 返回 false。
-c file检测文件是否是字符设备文件,如果是,则返回 true。[ -c $file ] 返回 false。
-d file检测文件是否是目录,如果是,则返回 true。[ -d $file ] 返回 false。
-f file检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。[ -f $file ] 返回 true。
-g file检测文件是否设置了 SGID 位,如果是,则返回 true。[ -g $file ] 返回 false。
-k file检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。[ -k $file ] 返回 false。
-p file检测文件是否是有名管道,如果是,则返回 true。[ -p $file ] 返回 false。
-u file检测文件是否设置了 SUID 位,如果是,则返回 true。[ -u $file ] 返回 false。
-r file检测文件是否可读,如果是,则返回 true。[ -r $file ] 返回 true。
-w file检测文件是否可写,如果是,则返回 true。[ -w $file ] 返回 true。
-x file检测文件是否可执行,如果是,则返回 true。[ -x $file ] 返回 true。
-s file检测文件是否为空(文件大小是否大于0),不为空返回 true。[ -s $file ] 返回 true。
-e file检测文件(包括目录)是否存在,如果是,则返回 true。[ -e $file ] 返回 true。

与上头的字符串运算一样, 文件测试运算同样也不能直接利用返回值, 只能放到流程控制语句中

file="/home/hhtxzzj/temp/makeFiles.sh"
if [ -r $file ]
then
   echo "文件可读"
else
   echo "文件不可读"
fi
if [ -w $file ]
then
   echo "文件可写"
else
   echo "文件不可写"
fi
if [ -x $file ]
then
   echo "文件可执行"
else
   echo "文件不可执行"
fi
if [ -f $file ]
then
   echo "文件为普通文件"
else
   echo "文件为特殊文件"
fi
if [ -d $file ]
then
   echo "文件是个目录"
else
   echo "文件不是个目录"
fi
if [ -s $file ]
then
   echo "文件不为空"
else
   echo "文件为空"
fi
if [ -e $file ]
then
   echo "文件存在"
else
   echo "文件不存在"
fi

image-20210507164524134

输入/输出重定向

参考博客:

https://www.runoob.com/linux/linux-shell-io-redirections.html

命令说明
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中默认的文件描述符:

  • 0 通常是标准输入(STDIN)
  • 1 是标准输出(STDOUT)
  • 2 是标准错误输出(STDERR)

这也是之前2>&1是将标准错误输出重定向到标准输出的操作缘由

重定向这里用到的比较多, 如果有啥不懂的再来看参考博客吧

引用外部脚本

Shell同样支持将一些公用的代码单独封装到一个文件中, 并在其他文件中引用

语法格式:

. filename   # 注意点号(.)和文件名中间有一空格

#或

source filename

注意, 引用文件中的内容也将被执行

栗子:

#! /bin/bash
# shellTest2.sh

msg="This is shellTest2.sh msg"
echo "$0 : msg = ${msg}"
#! /bin/bash
# shellTest.sh
source ./shellTest2.sh
echo "msg = ${msg}"

image-20210507171527429

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值