Shell脚本入门【小白友好】


提示:以下是本篇文章正文内容,下面案例可供参考

一、Shell脚本编写入门

1. Shell是什么?

       Shell是介于外部程序和操作系统内核之间的一层解释层,它作用是能够将用户输入的Linux命令翻译为操作系统能够调用或理解的指令。shell与操作系统及物理硬件的关系大致如下图所示:
在这里插入图片描述

2. Shell脚本

       Shell脚本是包含一系列命令的文本文件,通过编写一系列Shell命令、控制结构(如循环、条件语句等)、正则表达式和管道等机制能够完成自动化任务和管理计算机。例如,编写一个Shell脚本帮我们完成下载并运行MySQL服务。该脚本首先会创建相关文件夹用于保存相关数据,之后下载MySQL,然后进行相关信息配置,最后启动MySQL服务。而对于使用者只需指定Shell解释器执行该脚本即可,如果别的服务器也需要安装运行MySQL,只需要将该脚本上传并执行即可。通过编写Shell脚本完成自动化任务能够简化管理实现快速开发的目的。

3. Shell解释器

       Shell解释器是操作系统中负责解析和执行用户输入命令的程序。Shell脚本编写第一行往往会定义执行该脚本的解释器即”shebang“,如:#!/bin/bash。执行脚本时也可以指定解释器,如:sh helloword.sh。常见的解释器有以下几种:

  • Bash (Bourne-Again Shell):大多数现代Linux发行版默认使用的shell,功能丰富且兼容sh。
  • sh (Bourne Shell):最早的Unix shell之一,许多其他shell都基于或兼容sh语法。
  • csh (C Shell) 和 tcsh (TENEX C Shell):提供了一些类似于C语言语法结构的特性。
  • ksh (Korn Shell):由贝尔实验室开发,包含了sh的功能,并添加了许多增强特性。
  • zsh (Z Shell):提供了丰富的扩展功能,包括更强大的自动补全、提示符定制等

4. Shell脚本执行

编写一个输出"Hello world!"的shell脚本:

# 创建并编辑脚本
vim sayHelloWorld.sh
# 以下为脚本内容
#!/bin/bash
echo 'Hello World!'

PS:与Windows不同,在Linux中文件的后缀名并不决定该文件执行方式,只是作为类型区分的标识。例如,Shell脚本的后缀名可以是.config、.bat或者没有后缀名。

查看脚本权限:

新建的脚本是没有执行权限的,需要为脚本添加执行权限:

//所有用户均可执行
chmod a+x sayHelloWorld.sh

执行脚本:

  • 也可以使用sh  sayHelloWorld.sh的方式指定解释器运行脚本

二、Shell语法学习

1. Shell变量

       Shell变量主要包括:用户自定义变量、特殊变量和环境变量。在Shell中(如Bash)定义的变量默认类型都是字符串类型。下面依次介绍:

1.1. 用户自定义变量

       该变量是由用户自定义的变量,只在当前Shell中生效。声明方式为:变量名=变量值(注意:等号两边不能有空格

在这里插入图片描述

  • ${变量名} 和 $变量名都可以使用该变量的值,区别在于前者可以避免变量和后续字符串混淆。
  • 局部变量的声明方式为:local  变量名,该变量只在定义变量的函数范围内有效。

1.2. 特殊变量

       在Shell中以下变量具有特殊含义:

变量含义
$?表示上一条指令或函数的退出状态码,0表示成功,非零为失败
$0表示当前执行脚本的名称
$n表示当前脚本执行传递的第n个参数,n取值为1 - n
$#表示当前脚本执行参数的个数
$*$* 表示用空格将当前脚本执行传递的参数连接成一个字符串,"$*"表示意义同$*
$@$@ 表示用空格将当前脚本执行传递的参数连接成一个字符串,"$@"表示参数独立性会分别输出每个参数 。如:for arg in “$@” do echo “当前参数: ${arg}”
$!表示最近在后台运行进程的PID,运行进程时末尾加上&表示当前进程在后台执行

1.3. 环境变量

       环境变量又分为自定义环境变量(使用export声明,unset撤销变量)和内置环境变量,如:PATH、HOME和PWD等。

  • 使用以上方式声明的环境变量仅在当前Shell会话中有效,一旦打开一个新的会话,该变量就会失效。
  • 如果想让变量永久生效可以在用户家目录中的.bashrc中加入:export NAME=xiaoming,之后使用source重新加载配置文件或在用户下一次登录就会加载该变量。
    在这里插入图片描述
    以上操作仅对当前用户有效,定义全局变量即所有用户都可以访问到,可以修改/etc/profile。用户定义在/etc/profile和用户家目录中的.bashrc变量相同时会优先使用用户家目录中的配置。

2. 特殊符号

       以下符号均具有特殊含义,也是编写Shell脚本最常用的符号:

符号含义
;分割多个命令,使一行中的多个命令能够被顺序执行
&表示在后台运行指令或进程,如果&左右两边连接的是两个命令,如:command1 & command2那么表示立即在后台运行command1,在前台运行command2可以实现异步执行的效果
&&连接两个命令,表示第一个命令执行成功后第二个命令才会执行 与l l符号作用相反
>重定向输出,如 echo “Hello” > sayHelloWorld.sh,指令执行后覆盖sayHelloWorld.sh中的内容
>>追加输出,如: echo “Hello” > sayHelloWorld.sh,指令执行后会将Hello追加到sayHelloWorld.sh文件末尾
’ ’与其它语言一样,字符串原样返回
" "与其它语言一样,会解析特殊字符如”$(echo hello)“
` `优先执行` `中的命令并将命令的输出结果作为字符串插入到当前命令行中
|管道符,将前一个命令的执行结果传递给后一个命令
(())用于算术运算和整数比较。在双小括号内部,可以执行整数的加减乘除、位运算、比较运算以及变量的赋值等
[ ]用于条件测试,它是一个命令,用于测试文件属性、字符串比较和整数比较等

3. 数学运算及字符串操作

3.1 数学运算

       算术运算符号:

算数运算符含义
+、-、\ *、\ 、%加、减、乘、除 、取模
**幂运算
++、–自增、自减
||、&&、!或、与、非
==、!=、=是否相等、是否不等、=用于字符串比较相当于==
<<、>>左移位操作、右移位操作

       由于定义的变量为字符串类型,所以两个变量进行数学运算时需要不能直接使用运算符,例如:num1=1;num2=2;echo $num1+$num2输出结果为"1+2"。变量之间进行数学运算可以使用以下几种方式(进行乘法运算时需要使用\ *,*在Shell中是通配符):

  • 使用(( )):echo $((num1 + num2))
  • 使用[ ]:echo $[num1 + num2]
  • 使用expr:expr $num1 + $num2

3.2. 字符串操作

      定义变量str=abcdefg,有以下操作:

命令含义
echo ${#str}返回变量str的长度:7
echo ${str:2}从下标为2的字符开始截取到最后:cdefg
echo ${str:0:3}从下标为0的字符开始截取3个字符并返回
echo ${str/abc/hello}用hello替换第一个匹配到的abc子串:hellodefg
echo ${str/abc//hello}用hello替换所有匹配到的abc子串:hellodefg
echo ${str#abc}从变量开头删除最短匹配的abc子串,使用%表示从末尾匹配
echo ${str##abc}从变量末尾删除最长匹配的abc子串,使用%表示从末尾匹配

4. 条件表达式

       在Shell中使用[ ]和[[ ]]可以进行文件和字符串操作判断,如果是数值比较还可以使用(( ))。

  • [ ]和[[ ]]的区别:[[ ]]提供了更加强大的功能,而[ ]指提供较少功能,此外在[[ ]]中可以直接使用==和模式匹配,而[ ]则不适用。使用[ ]时两边必须有空格,如:[  -e “sayHelloWorld.sh”  ]

4.1. 操作符

  • 文件操作符:
操作符含义
[  -e  “sayHelloWorld.sh”   ]测试文件是否存在
[  -f   "sayHelloWorld.sh "  ]判断文件是否为普通文件
[  -d   “sayHelloWorld.sh”   ]测试文件是否为目录
[  -r   “sayHelloWorld.sh”   ]测试文件是否可读
[  -w  “sayHelloWorld.sh”   ]测试文件是否可写
[  -x  “sayHelloWorld.sh”   ]测试文件是否可执行
[  -s  “sayHelloWorld.sh”   ]测试文件是否为空(为空返回真
[  -h  “sayHelloWorld.sh”   ]测试文件是否为符号链接
  • 字符串操作符:
操作符含义
=检查两个字符串是否相等
!=检查两个字符串是否不相等
-z检查 字符串是否为空
-n检查字符串是否非空
  • 数值操作符,如果是浮点数比较需要使用bc或awk命令,如:
if (( $(echo "3.14 > 2.66" | bc -l) )); then
    echo "$num1 is greater than $num2"
fi
操作符含义
-eq检查两边的整数值是否相等
-ne检查两边的整数值是否不相等
-gt检查左边的整数值是否大于右边的整数值
-lt检查左边的整数值是否小于右边的整数值
-ge检查左边的整数值是否大于等于右边的整数值
-le检查左边的整数值是否小于等于右边的整数值

4.2. if语句语法

  • 单分支:
if <条件表达式>; then
	[语句1]
else
	[语句2]
fi 
## fi代表if语句结束,也可写成
if <条件表达式>
	then
		[语句1]
else
	[语句2]
fi 
  • 多分支
if <条件表达式>; then
	[语句1]
else
	[语句2]
fi 
## fi代表if语句结束,也可写成
if <条件表达式>
	then
		[语句1]
elif <条件表达式>
	then
		[语句2]
else
	[语句3]
fi 
  • 判断脚本输入参数是数字还是字母
#!/bin/bash

# 获取第一个参数
arg="$1"
# 使用正则表达式判断参数是否只包含数字或字母
if [[ $arg =~ ^-?[0-9]+$ ]]; then
    echo "入参是数字"
elif [[ $arg =~ ^[[:alpha:]]+$ ]]; then
    echo "入参是字母"
else
    echo "入参既非纯数字也非纯字母"
fi

4.3. case语句语法

  • 语法
casein
    模式1)
        commands...
        ;;
    模式2)
        commands...
        ;;
    模式N)
        commands...
        ;;
    *)
        default_commands...
        ;;
esac
  • 如果一个脚本允许多种执行模式,往往会使用到case语句
#!/bin/bash

action="$1"

case "$action" in
    start)
        echo "Starting the service..."
        ;;
    stop)
        echo "Stopping the service..."
        ;;
    restart)
        echo "Restarting the service..."
        ;;
    *)
        echo "Invalid action. Valid actions are: start, stop, restart."
        ;;
esac

5. 循环语句语法

       Shell中有三种循环结构分别是for循环、while循环、until循环,编写脚本是需要根据具体情况选择合适的循环结构。

5.1. for循环语句语法

# 最基础的for循环,常用于遍历列表或数组
for var in item1 item2 item3; do
  echo "$var"
done

# C-style的for循环,用于计数或迭代
for (( 初始化表达式 ; 循环条件 ; 更新表达式 )); do
  # 循环体内的命令
done
  • 用于打印从1到10的整数
#!/bin/bash

# for循环打印1到10的整数
for (( i=1; i<=10; i++ )); do
    echo "当前打印数: $i"
done

5.2. while循环语句语法

# 条件满足时持续执行循环体内的命令
while [ condition ]; do
  # 循环体内的命令
done

# 或者使用双括号语法
while [[ condition ]]; do
  # 循环体内的命令
done
  • 读取用户输入直到用户输入“quit”为止
#!/bin/bash

while true; do
    read -p "请输入(输入 quit 退出):" user_input
    if [[ $user_input == "quit" ]]; then
        break
    else
        echo "你输入的是:$user_input"
    fi
done

5.3. until循环语句语法

# 条件不满足时持续执行循环体内的命令
until [ 条件]; do
  # 循环体内的命令
done

# 或者使用双括号语法
until [[ condition ]]; do
  # 循环体内的命令
done
  • 用户输入密码,直到输入正确的密码才能进入系统:
#!/bin/bash

# 正确的密码
password="secret"

# 使用until循环直到正确输入密码
until [ "$input_password" = "$correct_password" ]; do
    # 请求用户输入密码
    read -s -p "请输入密码: " input_password
    # 检查密码是否正确
    if [ "$input_password" = "$password" ]; then
        echo "密码正确!"
        break
    else
        echo "密码错误,请重新输入。"
    fi
done

echo "欢迎进入系统!"

6. 函数定义与调用

6.1. 函数定义语法

#标准写法
function 函数名(){
	# 函数体
	# return可有可无
	return 返回值
}

#省略()写法
function 函数名{
	# 函数体
	# return可有可无
	return 返回值
}

# 省略function关键字写法,需要加()
函数名(){
	# 函数体
	# return可有可无
	return 返回值
}

6.2. 函数调用

  • 执行shell函数,只需要写函数名称即可不需要写( )
  • 函数调用要在函数定义后才能调用,因为Shell脚本是从上到下加载的
  • 使用local关键字定义函数体内的局部变量
  • 可以使用source加载另一个文件中定义的函数
#!/bin/bash

# 定义一个函数,接受两个参数
function greet {
    echo "你好, $1!"
    echo "你喜欢吃$2水果吗?"
}

# 调用函数并传入参数
greet "xiaoming" "草莓"

# 再次调用函数,传入不同的参数
greet "kangkang" "苹果"

7. Shell常用命令

7.1. echo 命令

       打印指定文本或变量值

命令含义
echo   -n ”Hello“不在输出末尾添加换行符。默认情况下,echo 命令会输出一个换行符
echo   -e ”Hello\t“会识别" "中的转义字符,与-E作用相反

7.2. wc命令

       统计文本或文件包含的字节、行数、单词数

命令含义
wc - l sayHelloWorld.sh统计sayHelloWorld.sh文件中的行数
wc - L sayHelloWorld.sh统计sayHelloWorld.sh文件中最长行的行数
wc - w sayHelloWorld.sh统计sayHelloWorld.sh文件中单词的个数(words,以空格、换行符、制表符等作为单词分隔符)
wc - m sayHelloWorld.sh统计sayHelloWorld.sh文件中字符的个数
wc - c sayHelloWorld.sh统计sayHelloWorld.sh文件中的字节数

7.3. read命令

       交互式读取用户输入的一行文本,并将其内容赋值给指定的变量,常用于函数中

命令含义
read var用户输入一行文本后,read 将这一行的内容赋值给 变量var
read -p "请输入您的名字: " name提示信息,指定用户输入之前显示的提示文本
read -t 5 -p "请输入您的名字: " name等待5秒,若无输入则退出
read -n 3 var读取用户输入文本的前三个字符并将其赋值给var变量
read -s -p "请输入密码: " password隐藏用户输入的内容
read -a array读取输入并将其以输入的空格为分割拆分成数组的元素
read -d ‘;’ var读取到指定的字符";"时停止

7.4. cut命令

       用于从文件或标准输入中截取指定的字符、字段或字节范围。它可以基于分隔符来切割文本,并将切割出来的部分输出到屏幕或重定向到其他文件。

命令含义
echo “Hello, World!” | cut -b1-5以字节为单位切割文件内容,输出Hello
echo “Hello, World!” | cut -c1-5与 -b 类似,但以字符为单位切割,而非字节,输出:Hello
echo “apple :banana :cherry” | cut -d: -f1指定字段(field)分隔符。默认分隔符是Tab,但可以更改。
echo ‘apple :banana :cherry’ | cut -d: -f1,3指定要截取的字段编号或范围,输出apple:cherry

7.5. grep命令

       用于搜索文本文件中匹配特定模式(如正则表达式)的行的命令

命令含义
grep ‘hello’ textfile.txt在 textfile.txt 文件中查找包含 “hello” 的行
grep -i ‘Hello’ textfile.txt忽略大小写,查找包含 “HELLO”、“hello” 等大小写形式的行
grep -v ‘hello’ textfile.txt反向匹配,输出不包含 “hello” 的行
grep -n ‘hello’ textfile.txt输出匹配行时显示对应的行号
grep -c ‘hello’ textfile.txt输出包含 “hello” 的行数
grep -e ‘hello’ -e ‘world’ textfile.txt查找包含 “hello” 或 “world” 的行
grep -E ‘hello|world’ textfile.txt查找包含 “hello” 或 “world” 的行
grep -w ‘hello’ textfile.txt查找完整单词 “hello”,而不是包含 “hello” 的其他单词,如 “hellos” 或 “yellow”
grep -A 2 ‘hello’ textfile.txt输出包含 “hello” 的行及其后两行
grep -B 2 ‘hello’ textfile.txt输出包含 “hello” 的行及其前两行
grep -C 2 ‘hello’ textfile.txt输出包含 “hello” 的行及其前后两行
grep -r ‘hello’ directory/在 directory/ 及其子目录下的所有文件中搜索包含 “hello” 的行

7.6. sed命令

       用于对文本进行查找、替换、删除、插入等多种行级别的编辑操作。它可以从文件、管道或其他输入源读取文本,对每一行进行处理后,将结果输出到屏幕或重定向到另一个文件

选项含义
-n抑制默认的打印操作,只有经过命令修改的行才会被打印
-i直接编辑原文件,如果不指定备份扩展名,则覆盖原文件;如果指定了备份扩展名(如 -i.bak),则在编辑前先备份原文件
-e允许在同一行内指定多个编辑命令
-f从指定的脚本文件中读取编辑命令
命令含义
s/pattern/replacement/flags替换命令,用 replacement 替换每一行中匹配 pattern 的部分。flags 可能包括:g:全局替换,同一行内所有匹配项都被替换、i:不区分大小写匹配、p:打印模式空间内容
sed -i ‘s/hello/hi/g’ file.txt替换 file.txt 中所有出现的 “hello” 为 “hi”
sed -i ‘/^$/d’ file.txt删除 file.txt 中的所有空白行
sed -n ‘/hello/p’ file.txt只打印 file.txt 中包含 “hello” 的行
sed -i ‘/^#/a This is an appended line.’ file.txt在注释行(以 # 开头的行)后面追加文本 “This is an appended line.”
sed -i ‘/^Start/i Inserted before’ file.txt在以 “Start” 开头的行之前插入文本 “Inserted before”

7.7. awk命令

awk 是一种强大的文本分析和报告生成工具,它能够处理每一行数据,并基于指定的模式和动作对数据进行处理。语法:awk ‘条件{ 执行动作}’ 文件

内置变量含义
$0整行的内容,表示当前处理的行
NF当前行的字段总数
NR当前已读取的记录行数,从1开始计数
FNR当前文件的记录数,也是从1开始计数,但在处理多个文件时,每处理一个新文件,FNR会重置为1
FILENAME当前正在处理的文件名
FS字段分隔符,默认是空格或制表符,通过 -F 选项更改
命令含义
awk ‘NR>2 {print}’ file.txt从第三行(NR 为3)开始,打印文件的每一行内容
awk -F: ‘{print $1, $2}’ /etc/passwd将默认的字段分隔符改为冒号并打印
awk ‘$2 >= 10 { print $1, $2 }’ input.txt根据条件打印特定字段
awk -v limit=5 ‘$1 > limit { print }’ input.txt在处理过程中使用变量limit,并打印出 input.txt 中第一列大于limit值的所有行
awk ‘{ sum += $1; n++ } END { print “Average:”, sum/n }’ numbers.txt计算 numbers.txt 文件中每一行第一个字段的总和,并计算平均值。END 关键字指示在所有行处理完成后执行的动作

三、实战

1. 编写一个使用安装并运行ngnix的脚本

   #!/bin/bash
   #创建挂载目录
   makeDir(){
   #进入到opt目录下
     cd /opt
     if [ -d "/ngnix" ]; then
       rm -rf /ngnix
       mkdir -p ./nginx/conf
       mkdir -p ./nginx/conf/conf.d
       mkdir -p ./nginx/html
       mkdir -p ./nginx/log
     else
       mkdir -p ./nginx/conf
       mkdir -p ./nginx/conf/conf.d
       mkdir -p ./nginx/html
       mkdir -p ./nginx/log
     fi
   }
   #检查Docker环境
   checkDockerEnv(){
   if ! command -v docker &> /dev/null
   then
       echo "Docker未安装,请先安装Docker。"
       exit 1
   else
       # 检查Docker守护进程是否正在运行
       if ! systemctl is-active --quiet docker
       then
           echo "启动docker..."
           systemctl start docker
           #删除已存在nginx容器和镜像
           nginx_containers=$(docker ps -aqf "name=nginx")
           if [[ -n "$nginx_containers" ]]; then
               docker stop $nginx_containers
               docker rm $nginx_containers
               docker rmi -f $nginx_containers
           fi
           nginx_image_ids=$(docker images --filter reference='*nginx*' -q)
           # 强制删除找到的所有镜像
           for image_id in $nginx_image_ids; do
               docker rmi -f $image_id
           done

       fi
   fi
   }
   runNginx(){
      #拉取nginx镜像
       echo "开始拉取Nginx镜像..."
       docker pull nginx
      #启动Nginx
       echo "启动nginx..."
       docker run -d \
       --name my_nginx \
       -p 80:80 \
       nginx
      #copy容器中的配置文件
       echo "copy配置文件..."
       docker cp my_nginx:/etc/nginx/nginx.conf /opt/nginx/conf
       docker cp my_nginx:/usr/share/nginx/html/index.html /opt/nginx/html
       docker cp my_nginx:/etc/nginx/conf.d /opt/nginx/conf
       #删除容器
       docker stop my_nginx
       docker rm my_nginx
       #重新启动
       echo "重新启动nginx..."
       docker run -d \
       --name my_nginx \
       -p 80:80 \
       -v /opt/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
       -v /opt/nginx/conf/conf.d:/etc/nginx/conf.d \
       -v /opt/nginx/html:/usr/share/nginx/html \
       -v /opt/nginx/log:/var/log/nginx \
       nginx
   }

   case "$1" in
       start)
           echo "Starting nginx service..."
           makeDir
           checkDockerEnv
           runNginx
           ;;
       restart)
           if [ -n "`docker ps -qf "name=my_nginx"`" ]; then
               echo "Restarting nginx service..."
               docker restart my_nginx
           else
               echo "The nginx service is not running"
           fi
            ;;
       status)
           if [ -n "`docker ps -qf "name=my_nginx"`" ]; then
               echo "The nginx service is running"
           else
               echo "The nginx service is not running"
           fi
           ;;
       *)
        echo "Usage: $0 {start|restart|status}"
        exit 1
        ;;
    esac
exit 0

总结

       Shell脚本的编写在开发中经常能够接触到,从基础Linux命令到完成自动化运维任务,只有不断的练习才能提高脚本的编写水平,本文介绍了Shell脚本的基础知识希望对大家学习Shell有所帮助(●’◡’●)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值