当前位置: 首页 > news >正文

Linux之Shell编程(一)

1. Shell 介绍

1.1 简介

Shell 是一个用 C 语言编写的程序,它作为用户与操作系统内核之间的桥梁,允许用户通过输入命令来访问和使用操作系统的内核服务。从功能上来说,它类似于 DOS 系统中的command.com和 Windows 系统中的cmd.exe

Shell 具有双重属性:它既是一种命令语言,用户可以通过它输入命令与系统交互;同时也是一种程序设计语言,支持变量、函数、流程控制等编程元素。通常所说的 Shell 编程,指的是编写 Shell 脚本(一系列命令的集合),而不是开发 Shell 解释器本身。

1.2 Shell 解释器

进行 Shell 编程需要两个基本工具:一个用于编写代码的文本编辑器(如 vi、vim、nano 等)和一个能够解释执行脚本的解释器。

Linux 系统中存在多种 Shell 解释器,常见的包括:

  • bash(Bourne Again SHell)
  • sh(Bourne SHell)
  • csh(C SHell)
  • ksh(Korn SHell)

可以通过执行cat /etc/shells命令查看当前系统已安装的所有 Shell 解释器。

在这些解释器中,bash 由于其易用性、功能丰富性以及免费开源的特点,在日常工作中被广泛使用,并且是绝大多数 Linux 发行版的默认 Shell 解释器。

2. 快速入门

2.1 编写 Shell 脚本

编写 Shell 脚本的基本步骤如下:

  1. 创建脚本文件:使用文本编辑器(如 vi)新建一个文件,文件名通常以.sh为扩展名(这只是一种约定,不影响脚本的实际执行),例如hello.sh

  2. 编写脚本内容:

    完整的hello.sh脚本内容如下:

    #!/bin/bash
    echo "Hello World !"
    
    • 脚本的第一行必须是#!/bin/bash(称为 shebang),这一行的作用是告诉操作系统该脚本需要使用 bash 解释器来执行。
    • 之后可以编写具体的命令,例如使用echo命令输出文本信息:echo "Hello World !"
  3. 赋予执行权限:刚创建的脚本文件默认只有读(r)和写(w)权限,没有执行(x)权限,需要通过chmod命令赋予执行权限。

    • 执行chmod +x ./hello.sh命令为脚本添加执行权限。
    • 可以通过ls -l hello.sh命令查看权限变化:未授权前显示为-rw-r--r--,授权后显示为-rwxr-xr-x

2.2 执行 Shell 脚本

执行 Shell 脚本主要有以下三种方式:

  1. 方式一:./hello.sh

    • 这种方式是在当前目录下直接执行脚本,./表示当前目录。
    • 执行前必须确保脚本已经获得执行权限(通过chmod +x设置)。
  2. 方式二:/root/shelldemo/hello.sh

    • 这种方式是通过指定脚本的完整路径来执行脚本,无论当前处于哪个目录都可以使用。
    • 同样需要提前给脚本赋予执行权限。
  3. 方式三:sh hello.sh(或sh /root/shelldemo/hello.sh

    • 这种方式是直接调用sh解释器,并将脚本文件作为参数传递给解释器。
    • 不需要提前给脚本赋予执行权限,因为此时是解释器在执行,而不是脚本本身直接执行。

需要注意的是,直接在命令行输入hello.sh执行脚本会失败,因为系统会到PATH环境变量所指定的目录中去查找该脚本,而当前目录通常不在PATH中,所以系统无法找到该脚本。

3. Shell 程序:变量

3.1 语法格式

变量定义的基本语法格式为变量名=值,定义时需要遵循以下规则:

  • 等号=两边不能有空格,例如name="zhangsan"是正确的,而name = "zhangsan"name= "zhangsan"都是错误的。
  • 变量名的首字符必须是字母(a-z 或 A-Z),不能以数字或其他符号开头。
  • 变量名中间可以包含字母、数字和下划线(_),但不能包含空格和标点符号。
  • 变量名不能使用 bash 的关键字(可以通过help命令查看 bash 的关键字列表)。

变量命名通常遵循 "见名知意" 的原则,单个单词一般使用小写字母,多个单词组成的变量名通常用下划线分隔,例如your_namestudent_agefile_path等。

3.2 变量使用

使用已定义的变量时,需要在变量名前加上$符号,具体方式有两种:

  1. $变量名:例如定义your_name="bigdata.com"后,执行echo $your_name可以输出变量的值。

  2. ${变量名}:这种方式与第一种效果相同,但可以帮助解释器更清晰地识别变量的边界,避免歧义。例如echo ${your_name}同样可以输出变量的值;当需要在变量后直接连接其他字符时,这种方式尤为有用,如echo ${your_name}123会输出bigdata.com123,而echo $your_name123则无法正确识别变量。

变量的重新赋值与删除:

  • 普通变量可以被重新赋值,例如先定义name="bigdata",之后可以执行name="hadoop"将变量值修改为 "hadoop"。

  • 使用readonly命令可以定义只读变量,语法为readonly 变量名=值(或先定义变量再执行readonly 变量名)。只读变量的值一旦设定就不能被修改,例如readonly name="bigdata"后,再执行name="hadoop"会报错。

  • 使用unset命令可以删除变量,语法为unset 变量名,例如unset name会删除name变量。但unset命令不能删除只读变量,尝试删除会报错。

3.3 变量类型

Shell 中的变量主要分为以下两类:

  1. 局部变量

    • 局部变量是在脚本或命令中定义的变量,仅在当前 Shell 实例中有效,其他 Shell 进程或由当前 Shell 启动的子进程无法访问这些变量。
    • 例如,在当前 Shell 窗口中定义name="hadoop",执行echo $name可以正常输出 "hadoop";但如果打开一个新的 Shell 窗口(新的 Shell 实例),再执行echo $name则无法输出任何内容,因为该变量在新的 Shell 实例中不存在。
  2. 全局变量(环境变量)

    • 全局变量(环境变量)是可以被所有程序(包括当前 Shell 和由它启动的所有子进程)访问的变量。一些系统程序和应用程序需要依赖特定的环境变量才能正常运行。
    • 可以使用set命令查看当前 Shell 中的所有变量(包括局部变量和环境变量),使用envprintenv命令可以只查看环境变量。
    • 常见的环境变量有PATH(系统查找命令的路径)、HOME(当前用户的主目录)、USER(当前登录的用户名)等。

4. 字符串

字符串是 Shell 编程中最常用的数据类型之一,字符串的表示方式有以下几种:

4.1 单引号字符串

单引号字符串的特点:

  • 单引号内的所有字符都会原样输出,变量在单引号字符串中不会被解析,例如:

    skill='linux'
    str='I am good at $skill'
    echo $str  # 输出:I am good at $skill
    
  • 单引号

  • 字符串中不能出现单独的单引号(即使使用转义符\转义也不行),但可以成对出现单引号来实现字符串拼接,例如:

    str='He said ''Hello'' to me'
    echo $str  # 输出:He said Hello to me
    

4.2 双引号字符串

双引号字符串相比单引号字符串更加灵活,具有以下特点:

  • 双引号内的变量会被解析并替换为其实际值,例如:

    skill='linux'
    str="I am good at $skill"
    echo $str  # 输出:I am good at linux
    
  • 双引号内可以使用转义符\来转义特殊字符,例如:

    str="He said \"Hello\" to me"
    echo $str  # 输出:He said "Hello" to me
    

4.3 获取字符串长度

使用${#字符串变量名}可以获取字符串的长度,例如:

skill='hadoop'
echo ${#skill}  # 输出:6(因为"hadoop"有6个字符)

4.4 提取子字符串

Shell 中可以通过以下方式提取子字符串:

  1. 从指定索引开始截取到字符串末尾:${字符串变量名:起始索引}(索引从 0 开始),例如:

    str="I am good at hadoop"
    echo ${str:2}  # 输出:am good at hadoop(从索引2开始截取)
    
  2. 从指定索引开始截取指定长度的子字符串:${字符串变量名:起始索引:长度},例如:

    str="I am good at hadoop"
    echo ${str:2:2}  # 输出:am(从索引2开始,截取2个字符)
    echo ${str:5:4}  # 输出:good(从索引5开始,截取4个字符)
    

    注意:字符串中的空格也会被算作一个字符。

4.5 查找子字符串

使用expr index "$字符串变量名" 子字符串可以查找子字符串中任意字符在原字符串中首次出现的位置(位置从 1 开始计算),例如:

str="I am good at hadoop"
echo `expr index "$str" am`  # 输出:3(字符'a'在原字符串中首次出现的位置是3)
echo `expr index "$str" do`  # 输出:7(字符'o'在原字符串中首次出现的位置是7)

注意:该命令的结果是子字符串中任意字符在原字符串中最早出现的位置,而不是整个子字符串的位置。

5. Shell 程序:参数传递

5.1 参数传递方式

在执行 Shell 脚本时,可以向脚本传递参数,具体方式如下:

  • 传递参数:执行脚本时,在脚本文件名后面加上参数,参数之间用空格分隔,格式为./脚本名 参数1 参数2 参数3 ...,例如./hello.sh A 100 "test"

  • 在脚本内部获取参数:使用$n的形式来获取传递的参数,其中n是一个数字:

    • $0表示当前脚本的名称(包括路径,取决于执行脚本的方式)。
    • $1表示第一个参数,$2表示第二个参数,以此类推,$9表示第九个参数,${10}表示第十个参数(当数字大于 9 时,需要用花括号包裹)。

示例:编写hello.sh脚本,内容如下:

#!/bin/bash
echo "脚本名称:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"

执行./hello.sh A 100 "test",输出结果为:

脚本名称:./hello.sh
第一个参数:A
第二个参数:100
第三个参数:test

5.2 特殊字符

Shell 中有一些特殊的变量,用于处理传递的参数,常用的有:

  • $#:表示传递到脚本的参数的个数,例如:

    # 脚本内容
    echo "参数个数:$#"
    # 执行./test.sh 1 2 3,输出:参数个数:3
    
  • $*:以一个单字符串的形式显示所有传递给脚本的参数,例如:

    # 脚本内容
    echo "所有参数:$*"
    # 执行./test.sh 1 2 3,输出:所有参数:1 2 3
    
  • $$:表示当前脚本运行的进程 ID 号(PID),例如:

    # 脚本内容
    echo "当前进程ID:$$"
    # 执行脚本会输出当前脚本的进程ID
    
  • $!:表示最后一个在后台运行的进程的 ID 号,例如:

    # 脚本内容
    sleep 100 &
    echo "后台进程ID:$!"
    # 执行脚本会输出sleep进程的ID
    
  • $@:与$*类似,也用于显示所有传递给脚本的参数,但在使用引号括起来时,$@会将每个参数作为一个独立的字符串,而$*会将所有参数作为一个整体字符串,例如:

    # 脚本内容
    echo "使用\$@:$@"
    # 执行./test.sh 1 2 3,输出:使用$@:1 2 3
    
  • $?:表示最后一个命令的退出状态。通常,0 表示命令执行成功,非 0 表示命令执行失败,例如:

    # 执行成功的命令
    ls
    echo $?  # 输出:0
    # 执行失败的命令
    ls non_existent_file
    echo $?  # 输出:2(不同命令的错误码可能不同)
    

5.3 $*和$@的区别

$*$@在大多数情况下表现相似,但在被双引号""包含时,它们的行为有所不同:

  • 当不被双引号包含时,$*$@都以$1 $2 ... $n的形式展开所有参数,例如:

    # 脚本内容
    for arg in $*; doecho "参数:$arg"
    done
    echo "---------"
    for arg in $@; doecho "参数:$arg"
    done
    # 执行./test.sh "a b" c,两者输出相同:
    # 参数:a
    # 参数:b
    # 参数:c
    # ---------
    # 参数:a
    # 参数:b
    # 参数:c
    
  • 当被双引号包含时,"$*"会将所有参数合并为一个字符串,形式为"$1 $2 ... $n";而"$@"会将每个参数都作为一个独立的字符串,形式为"$1" "$2" ... "$n",例如:

    # 脚本内容
    for arg in "$*"; doecho "参数:$arg"
    done
    echo "---------"
    for arg in "$@"; doecho "参数:$arg"
    done
    # 执行./test.sh "a b" c,输出:
    # 参数:a b c
    # ---------
    # 参数:a b
    # 参数:c
    

6. Shell 程序:运算符

Shell 支持多种运算符,主要包括算术运算符、关系运算符、逻辑运算符、字符串运算符和文件测试运算符。

6.1 算术运算符

原生的 bash 不支持直接进行算术运算,需要借助其他方式来实现,常用的有以下几种:

  1. 使用expr命令

    • expr是一个用于表达式求值的工具,可以进行基本的算术运算。
    • 语法格式:result=expr 运算式 ``(注意:运算式中的运算符两边必须有空格,且整个表达式需要用反引号` 包含)。
    • 支持的算术运算符:+(加)、-(减)、*(乘,需要转义\*)、/(除)、%(取余)。
    • a=10
      b=3
      echo `expr $a + $b`  # 输出:13
      echo `expr $a - $b`  # 输出:7
      echo `expr $a \* $b` # 输出:30(乘法需要转义)
      echo `expr $a / $b`  # 输出:3(整数除法)
      echo `expr $a % $b`  # 输出:1
      
  2. 使用(( ))

    • 这种方式可以直接在双括号中进行算术运算,支持自增(++)、自减(--)等操作。
    • 示例:
      a=5
      b=2
      ((c = a + b))
      echo $c  # 输出:7
      ((a++))  # a自增1,变为6
      echo $a  # 输出:6
      ((b--))  # b自减1,变为1
      echo $b  # 输出:1
      
  3. 使用$(( ))

    • 这种方式可以将算术运算的结果赋值给变量,语法为变量=$((运算式))
    • 示例:
      a=10
      b=4
      c=$((a - b))
      echo $c  # 输出:6
      d=$((a * b))
      echo $d  # 输出:40
      
  4. 使用$[ ]

    • 这种方式与$(( ))类似,也可以进行算术运算,语法为变量=$[运算式]
    • 示例:
      a=8
      b=3
      c=$[a + b]
      echo $c  # 输出:11
      d=$[a / b]
      echo $d  # 输出:2
      

6.2 关系运算符

关系运算符主要用于比较两个数字(字符串形式的数字也可以),返回布尔值(true 或 false)。在 Shell 中,true 通常用 0 表示,false 用非 0 表示。常用的关系运算符有:

  • -eq:检测两个数是否相等,相等返回 true。

    a=5; b=5
    if [ $a -eq $b ]; then echo "相等"; else echo "不相等"; fi  # 输出:相等
    
  • -ne:检测两个数是否不相等,不相等返回 true。

    a=5; b=6
    if [ $a -ne $b ]; then echo "不相等"; else echo "相等"; fi  # 输出:不相等
    
  • -lt:检测左边的数是否小于右边的数,是则返回 true。

    a=3; b=5
    if [ $a -lt $b ]; then echo "小于"; else echo "不小于"; fi  # 输出:小于
    
  • -gt:检测左边的数是否大于右边的数,是则返回 true。

    a=7; b=5
    if [ $a -gt $b ]; then echo "大于"; else echo "不大于"; fi  # 输出:大于
    
  • -le:检测左边的数是否小于或等于右边的数,是则返回 true。

    a=5; b=5
    if [ $a -le $b ]; then echo "小于等于"; else echo "大于"; fi  # 输出:小于等于
    
  • -ge:检测左边的数是否大于或等于右边的数,是则返回 true。

    a=6; b=5
    if [ $a -ge $b ]; then echo "大于等于"; else echo "小于"; fi  # 输出:大于等于
    

6.3 逻辑运算符

逻辑运算符用于连接多个条件,进行逻辑运算,常用的有:

  • -a:逻辑与,当两个条件都为 true 时,结果为 true。

    a=10; b=5; c=0
    if [ $a -gt $b -a $a -gt $c ]; then echo "两个条件都成立"; else echo "至少一个条件不成立"; fi  # 输出:两个条件都成立
    
  • -o:逻辑或,当两个条件中至少有一个为 true 时,结果为 true。

    a=10; b=100; c=0
    if [ $a -gt $b -o $a -gt $c ]; then echo "至少一个条件成立"; else echo "两个条件都不成立"; fi  # 输出:至少一个条件成立
    
  • &&:逻辑与,与-a类似,但使用方式不同,通常用于(( ))或命令之间。

    a=10; b=5; c=0
    if (( $a > $b && $a > $c )); then echo "两个条件都成立"; else echo "至少一个条件不成立"; fi  # 输出:两个条件都成立
    
  • ||:逻辑或,与-o类似,通常用于(( ))或命令之间。

    a=10; b=100; c=0
    if (( $a > $b || $a > $c )); then echo "至少一个条件成立"; else echo "两个条件都不成立"; fi  # 输出:至少一个条件成立
    

6.4 字符串运算符

字符串运算符用于处理和比较字符串,常用的有:

  • -n STRING:检测字符串的长度是否不为零(即字符串非空),如果是则返回 true。

    str="hello"
    if [ -n $str ]; then echo "字符串非空"; else echo "字符串为空"; fi  # 输出:字符串非空
    
  • -z STRING:检测字符串的长度是否为零(即字符串为空),如果是则返回 true。

    str=""
    if [ -z $str ]; then echo "字符串为空"; else echo "字符串非空"; fi  # 输出:字符串为空
    
  • =:判断两个字符串是否相等,如果相等则返回 true。

    str1="hello"; str2="hello"
    if [ $str1 = $str2 ]; then echo "字符串相等"; else echo "字符串不相等"; fi  # 输出:字符串相等
    
  • !=:判断两个字符串是否不相等,如果不相等则返回 true。

    str1="hello"; str2="world"
    if [ $str1 != $str2 ]; then echo "字符串不相等"; else echo "字符串相等"; fi  # 输出:字符串不相等
    

6.5 文件测试运算符

文件测试运算符用于检测文件的各种属性,常用的有:

  • -f 文件名:检测文件是否存在且是一个普通文件(不是目录)。

    if [ -f ./test.sh ]; then echo "是普通文件"; else echo "不是普通文件或不存在"; fi
    
  • -d 目录名:检测文件是否存在且是一个目录。

    if [ -d ./testdir ]; then echo "是目录"; else echo "不是目录或不存在"; fi
    
  • -s 文件名:检测文件是否存在且不为空(文件大小大于 0)。

    if [ -s ./test.txt ]; then echo "文件存在且不为空"; else echo "文件不存在或为空"; fi
    
  • -e 文件名:检测文件(包括普通文件、目录等)是否存在。

    if [ -e ./test ]; then echo "文件存在"; else echo "文件不存在"; fi
    
  • -r 文件名:检测文件是否存在且当前用户具有读权限。

    if [ -r ./test.sh ]; then echo "有读权限"; else echo "没有读权限或文件不存在"; fi
    
  • -w 文件名:检测文件是否存在且当前用户具有写权限。

    if [ -w ./test.sh ]; then echo "有写权限"; else echo "没有写权限或文件不存在"; fi
    
  • -x 文件名:检测文件是否存在且当前用户具有执行权限。

    if [ -x ./test.sh ]; then echo "有执行权限"; else echo "没有执行权限或文件不存在"; fi
    

7. 总结

  • shell是什么:命令解释器,连接用户与 Linux 内核,转译命令并反馈结果
  • shell能做什么:自动化部署、批量操作(如加用户)、备份数据库、探测负载等,解决重复工作省时间
  • 脚本构成:首行#!/bin/bash(指定解释器),#开头是注释
  • 脚本步骤:写命令→chmod +x 脚本名赋权限→检查→执行
  • 脚本执行方式./脚本名(需权限)、sh 脚本名(无需权限)、source 脚本名(无需权限)
  • 变量变量名=值(无空格,字母 / 下划线开头),用$变量名调用;可修改(普通)、设只读(不可改删)、unset删普通变量;分全局(全环境用)和局部(当前用)
  • 字符串:单引号不识别变量,双引号识别;${#串}查长度,${串:索引}提取,expr index查找(单字符,从 1 算)
  • 参数./脚本 参1 参2传递,$1取参 1、$#参个数、$*整串输出、$@列表输出
  • 运算:算术用+、-、\*、/、%;逻辑用&&(与)、||(或)、!(否)
http://www.dtcms.com/a/355931.html

相关文章:

  • 异步方法和多线程有什么区别,他们的实现逻辑是什么以及为什么异步方法: 不能和调用者在同一个类中
  • VisionPro联合编程控件导入WinFrom以及VS卡死问题
  • GCC版本和C语言标准版本的对应关系
  • 一个Demo射击小计(纯蓝图)
  • 前端学习 10-1 :验证中的UVM
  • .Net Core Web 架构(管道机制)的底层实现
  • jadx反向编译JAR包
  • 基于SQL数据库的智能问答系统设计与实现
  • Codeforces Round 1043 (Div. 3) D. From 1 to Infinity
  • 2025年9月计算机二级C++语言程序设计——选择题打卡Day9
  • 【数据分享】珠江三角洲水系地理空间全套数据集
  • x64dbg的基本调试操作 (未完,待补充)
  • 通信协议再升级,PROFINET和EtherNet IP网关迎接改造升级大挑战
  • 智慧清洁革新者:有鹿机器人自述
  • @Jenkins 介绍、部署与使用标准作业程序
  • 深入 OpenHarmony 内核:设备待机管理模块的休眠调度与资源节能技术
  • AT_abc407_f [ABC407F] Sums of Sliding Window Maximum
  • 告别低效!三坐标测量机提高油缸导向套检测效率
  • 拷贝构造和赋值重载有什么区别
  • 转发、重定向
  • 什么是强化学习? ——— 帮助新手了解
  • 基于51单片机的远程wifi浇花系统设计
  • Snagit 2025.3.0 截图贴图录像编辑
  • Android Keystore签名文件详解与安全防护
  • shell编程学习
  • 基于深度学习的档案级图像修复:Coderformer AI技术解析与应用实践
  • 一、晶振与布局布线处理
  • Python Imaging Library (PIL) 全面指南:Python Imaging Library (PIL)基础图像处理入门
  • 呼叫中心录音加密与数据隔离技术方案全解析
  • Wagtail 扩展 HomePage 模型(一个简单的 例子)