引言

  在人工智能中,Shell编程在Linux中是非常重要的。当我们需要批量处理成千上万的训练数据、自动化模型训练流程时,手动一个个输入命令就像用勺子舀干大海。Shell脚本正是解决这些重复性工作的神奇工具,它能将复杂的操作封装成简单的命令,让计算机自动完成繁重的工作。掌握Shell编程将为我们的AI之路增添强大的翅膀。让我们从最基础的概念开始,一步步学习Shell编程。

一、Shell编程简介

1、什么是Shell

  Shell可以理解为用户和Linux系统内核之间的"翻译官"。当我们输入命令时,Shell负责理解我们的意图,然后告诉系统内核该做什么。简单来说:用户 → 输入命令 → Shell → 翻译命令 → 系统内核 → 执行操作,我们今天要学习的是命令行Shell也就是文本界面。Linux的Shell的种类有很多最常见的有:
1)、Bash (我们今天要学习的):最流行,大多数Linux系统的默认Shell
2)、Zsh (Z Shell):功能丰富,有很多好用的特性
3)、Fish (Friendly Interactive Shell):对新手友好

2、Shell环境

  Shell 编程跟其他编程一样,只需要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器。Shell环境就像是一个"工作空间",里面存放了很多设置和信息,比如:当前用户在什么位置、系统在哪里查找命令、各种程序的配置信息。

  PATH变量告诉Shell在哪里寻找命令。当输入一个命令时,Shell会按照PATH中的路径顺序查找。

# 查看PATH
echo $PATH# echo:输出命令,用于显示文本
#PATH中的路径用冒号:分隔,Shell会按顺序在这些路径中查找命令

3、 第一个Shell脚本

  我们来创建一个简单的Shell脚本来感受一下:用vi/vim命令来创建文件,新建一个文件cc.sh, 输入代码,第一行一般是:#!/bin/bash(它告诉系统这个脚本需要什么解释器来执行)

vim cc.sh
#!/bin/bash
# 这是我的第一个Shell脚本
echo "这是我的第一个Shell脚本!"
echo "脚本运行成功!"

4、Shell脚本运行方式

  Shell脚本有多种运行方式:

1)、使用bash命令运行(推荐新手使用)

bash cc.sh

2)、添加执行权限后直接运行

chmod +x cc.sh    # 给文件添加执行权限
./cc.sh           # 运行脚本

3)、使用source命令运行

source cc.sh

4)、使用点号运行(与source相同)

.cc.sh

  这里我们使用第一种方式:

bash cc.sh

  运行成功。

二、Shell基础语法详解

1、变量

  变量在任何一种编程语言里都是不可或缺的,变量用来存放各种数据。变量就像是一个"储物箱",可以存放数据,需要的时候再取出来用。和其他编程语言不同的是在 Bash shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。

1)、定义和使用变量

  Shell中有三种定义变量的方式:直接赋值、用单引号、用双引号。要注意的是在Shell中=的周围是不能有空格的,这里用$来使用变量。变量名外的花括号{ }加不加都行,加花括号是为了帮助解释器识别变量的边界,建议给所有变量加上花括号{ },这是个良好的编程习惯。

#!/bin/bash
# 变量使用示例

# 定义变量(注意:等号两边不能有空格)
name="小王"
age=18
# 使用变量(用$符号)
echo "学生姓名:${name}"
echo "年龄:${age}"
# 重新赋值
name="小红"
echo "修改后的姓名:${name}"

  这里提一下用单引号和用双引号有什么区别,当使用单引号包围变量时,单引号内的内容会被原样输出,不会对其中的变量或命令进行解析。这种方式适用于需要显示纯字符串的场景,即不希望系统对其中的内容进行任何处理。而当使用双引号包围变量时,双引号内的内容会在输出前被解析,其中的变量和命令会被替换为实际值。这种方式适用于字符串中包含变量或命令,并且希望它们被执行或展开后的输出场景。例如:

#!/bin/bash
a='Hello $USER'
echo $a

a="Hello $USER"
echo $a

2)、变量命名规则

  Shell中变量的命名规则和其他编程语言一样:只能包含字母、数字、下划线,不能以数字开头,建议使用有意义的名称。

#!/bin/bash
# 不同类型的变量
# 字符串
name="小王"
course="人工智能"
# 数字
age=20
count=80
# 布尔值(实际上用字符串表示)
is_student="true"
is_working="false"
echo "姓名:${name}"
echo "课程:${course}"
echo "年龄:${age}"
echo "计数:${count}"
echo "是否学生:${is_student}"

3)、只读变量

  readonly 命令可以将变量定义为只读变量,只读变量的值只能读取不能被改变。

#!/bin/bash
# 只读变量(常量)
readonly name="小王"
echo "姓名:${name}"
# 尝试修改只读变量(会报错)
name = "小红"#这行会报错

4)、删除变量

  unset 命令可以删除变量。

#!/bin/bash
# 删除变量
a="临时数据"
echo "删除前:${a}"
unset a    # 删除变量
echo "删除后:${a}"  # 输出空行

2、传递参数

  当我们运行脚本时,可以向脚本传递额外的信息,这些信息就是参数。

#!/bin/bash
# 参数传递示例
echo "脚本名称:$0"
echo "第一个参数:$1"
echo "第二个参数:$2" 
echo "第三个参数:$3"
echo "参数总数:$#"
echo "所有参数:$@"
# 使用示例
echo "你好,$1!欢迎学习$2。"

  我们填入参数后运行脚本。

bash cc.sh 小王 Shell编程

  参数的特殊变量:

变量	说明
$0	脚本名称
$1 - $9	第1-9个参数
$#	参数个数
$@	所有参数列表
$*	所有参数作为一个整体

3、字符串

  字符串是由一个或多个字符组成的序列。这些字符可以是字母、数字、符号等。在编程中,字符串是一种非常基础的数据类型。

1)、字符串的基本操作

  字符串可以通过拼接操作将几个字符串组合成一个新字符串。${#str} 语法用来获取字符串长度。字符串的截取操作方法为: ${string:start:length} 对字符串可以进行不同位置的截取。下面将例举Bash 中常见的字符串操作方法。

str1="Hello"
str2="World"
# 字符串拼接
a="${str1} ${str2}"
echo "拼接结果:${a}"
# 字符串长度
echo "str1长度:${#str1}"
echo "str2长度:${#str2}"
# 字符串截取
message="Welcome to AI  人工智能"
echo "前7个字符:${message:0:7}"
echo "从第8个开始:${message:8}"
echo "最后5个字符:${message:0-5}"

  这里额外介绍几种方法:

1.   # 号

    用#号可以截取第一个指定字符右边的所有字符。

${string#*chars}   #string表示要截取的字符,chars是指定的字符串,
*是通配符的一种,表示任意长度的字符串。
a="hello world"
echo ${a#*l:}#截取第一个l右边所有的字符

2.##号

    ##号可以从最后一个指定字符开始截取

#!/bin/bash
a="hello world"
echo ${a##*l}#截取最后一个l右边所有的字符

3.%号

    用%号可以截取最后一个指定字符串左边的所有字符。

${string%chars*}
#!/bin/bash
a="hello world"
echo ${a%l*}#截取最后一个l左边所有的字符

4.%%号

  用%%号可以截取第一个指定字符串左边的所有字符。

#!/bin/bash
a="hello world"
echo ${a%%l*}#截取第一个l左边所有的字符

2)、字符串查找和替换

#!/bin/bash
# 字符串查找和替换
text="I love Python"
# 替换第一个匹配
echo "${text/Python/Shell}"

# 替换所有匹配
echo "${text//o/O}"

# 删除匹配部分
filename="cc.tar.gz"
echo "删除前缀:${filename#*.}"
echo "删除后缀:${filename%.*}"

4、数组

  数组就是可以存放多个值的"储物柜",每个值都有一个编号(索引)。Shell 数组用括号来表示,元素用"空格"符号分割开。

数组名=(value1 value2 ...valuen)

  我们可以通过 ${数组名[0]} 访问数组中的元素,也可以用${数组名[@]}一次性访问所有元素。还能通过 ${#数组名[@]} 获取并打印数组的长度。数组还可以被for循环遍历。数组里的值也可以进行修改和添加。

#!/bin/bash                                                                                              
# 数组操作示
# 定义数组
a=("apple" "banana" "cherry" "date")
b=(10 20 30 40 50)
# 访问数组元素
echo "第一个水果: ${a[0]}"
echo "所有水果: ${a[@]}"
echo "水果个数: ${#a[@]}"
# 遍历数组
echo "--- 水果列表 ---"
for a in "${a[@]}"; do
    echo "水果: ${a}"
done
# 数组操作
a[1]="blueberry"  # 修改元素
a+=("elderberry") # 添加元素
unset a[2]        # 删除元素

echo "--- 修改后的数组 ---"
for i in "${!a[@]}"; do
    echo "索引 $i: ${a[$i]}"
done

5、运算符

  Shell中也提供了多种运算符,包括算术运算符、关系运算符、布尔运算符、字符串运算符和逻辑运算符。与大多数编程语言不同的是,Shell中的表达式和运算符之间必须有空格,例如 2 + 2 是正确的写法,而 2+2 则是错误的。此外,完整的表达式需要被反引号(`)包围,注意这个符号并不是单引号,而是在键盘的 Esc 键下方。

1)、算数运算符

  要用$(( ))算术扩展语法来实现基本的数学运算:加法、减法、乘法、除法、取余。

#!/bin/bash
# 算术运算示例

a=9
b=3

echo "a = ${a}, b = ${b}"
echo "加法:$((a + b))"
echo "减法:$((a - b))" 
echo "乘法:$((a * b))"
echo "除法:$((a / b))"
echo "取余:$((a % b))"

# 其他写法
c=$((a + b))
echo "a + b = ${c}"

2)、关系运算符

  在编程语言中关系运算符有一个重要限制:它们通常只用于数字之间的比较,而不能直接用于字符串,除非该字符串的内容完全由数字组成。

-eq    #检测两个数是否相等,相等返回True
-ne    #检测两个数是否不相等,不相等返回True
-gt    #检测左边的数是否大于右边的,是则返回True
-lt    #检测左边的数是否小于右边的,是则返回True
-ge    #检测左边的数是否大于等于右边的,是则返回True
-le    #检测左边的数是否小于等于右边的,是则返回True
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"
fi
if [ $a -lt $b ]; then
    echo "a小于b"
fi
if [ $a -ge $b ]; then
    echo "a大于等于b"
fi
if [ $a -le $b ]; then
    echo "a小于等于b"
fi

3)、布尔运算符和逻辑运算符

  布尔运算符通常与test命令或方括号[ ]配合使用,用于组合多个条件判断,构成复杂的逻辑表达式。

i    #非运算,表达式ture则返回false否则返回true
-o    #或运算,有一个为true则返回true
-a    #与运算,两个都为true返回true
&&    #逻辑的and
||    #逻辑的or
#!/bin/bash
a=5
b=10
echo "a=${a}, b=${b}"
echo ""

# 非运算 (!)
echo "非运算 (!):"
if ! [ $a -eq 3 ]; then
    echo "a不等于3,所以返回 true"
fi
# 或运算 (-o)
echo ""
echo " 或运算 (-o):"
if [ $a -eq 5 -o $b -eq 8 ]; then
    echo " a等于5 或 b等于8,有一个为真,返回 true"
fi

# 与运算 (-a)
echo ""
echo "与运算 (-a):"
if [ $a -eq 5 -a $b -eq 10 ]; then
    echo "a等于5 且 b等于10,两个都为真,返回 true"
fi
echo ""
echo "现代写法:"
if [ $a -eq 5 ] && [ $b -eq 10 ]; then
    echo " 两个都为真,返回 true"
fi

if [ $a -eq 3 ] || [ $b -eq 10 ]; then
    echo "有一个为真,返回 true"
fi

4)、字符串运算符

  Shell中用于字符串比较和检测的有五个常用运算符:=、!=、-z、-n、$。这些运算符在条件判断和流程控制中非常有用,能够帮助脚本根据字符串的不同状态执行相应的操作。  

=    #检测两个字符串是否相等,相等返回True
!=   #检测两个字符串是否不相等,不相等返回True
-z   #检测字符串长度是否是0,是0返回True
-n   #检测字符串长度是否不是0,不是0返回True
$    #检测字符串是否不为空,不为空返回True
#!/bin/bash
a="qwert"
b="asdfg"
empty_str=""
echo "a=${a}, b=${b}, empty_str=${empty_str}"
echo ""

# = 检测两个字符串是否相等
if [ "$a" = "qwert" ]; then
    echo "字符串相等,返回 true"
fi

if [ "$a" = "$b" ]; then
    echo "这个不会执行"
else
    echo "字符串不相等,返回 false"
fi

# != 检测两个字符串是否不相等
echo ""
if [ "$a" != "$b" ]; then
    echo "字符串不相等,返回 true"
fi

if [ "$a" != "qwert" ]; then
    echo "这个不会执行"
else
    echo "字符串相等,返回 false"
fi

# -z 检测字符串长度是否为0
echo ""
if [ -z "$empty_str" ]; then
    echo "空字符串,返回 true"
fi

if [ -z "$a" ]; then
    echo "这个不会执行"
else
    echo "字符串不为空,返回 false"
fi

# -n 检测字符串长度是否不为0
echo ""
if [ -n "$a" ]; then
    echo "字符串长度不为0,返回 true"
fi

if [ -n "$empty_str" ]; then
    echo "这个不会执行"
else
    echo "字符串为空,返回 false"
fi

# $ 检测字符串是否不为空 (直接使用变量)
echo ""
if [ "$a" ]; then
    echo "字符串不为空,返回 true"
fi

if [ "$empty_str" ]; then
    echo "这个不会执行"
else
    echo "字符串为空,返回 false"
fi

        

6、echo命令

  Linux中echo命令有多种用法。首先有基本输出功能,还能输出变量内容,并且用-e参数可以转义字符,-n参数实现不换行输出,而且有两种方式将输出重定向到文件:覆盖写入(>)和追加写入(>>)。

#!/bin/bash
# echo命令示例
# 基本输出
echo "Hello World"
# 输出变量
name="小王"
echo "你好,${name}"

# 输出特殊字符
echo -e "第一行\n第二行"    # \n 换行
echo -e "姓名:\t小王"       # \t 制表符

# 输出不换行
echo -n "正在加载..."
echo "完成!"

# 输出到文件
echo "这是日志内容" > log.txt
echo "追加内容" >> log.txt

7、printf命令

  Shell脚本中printf命令有格式化输出的功能。该命令提供了比echo更精确的输出控制,支持类似C语言的格式化语法:%s用于字符串输出,%d用于整数输出,%.2f用于保留两位小数的浮点数输出。可以单个变量格式化、多参数同时格式化,以及通过指定字段宽度(如%-10s表示左对齐的10字符宽度)创建整齐对齐的表格输出。

#!/bin/bash
# printf命令示例
# 基本格式化
printf "姓名:%s\n" "小王"
printf "年龄:%d\n" 18
printf "成绩:%.2f\n" 90

# 多个参数
printf "学生信息:%s, %d岁, 成绩%.2f\n" "小王" 18 90

# 表格对齐
echo "=== 学生成绩表 ==="
printf "%-10s %-8s %-10s\n" "姓名" "年龄" "成绩"
printf "%-10s %-8d %-10.2f\n" "小王" 18 90
printf "%-10s %-8d %-10.2f\n" "小红" 16 88

8、read命令

   Shell脚本中read命令有多种输入方式。该命令用于从标准输入读取用户数据,支持多种实用参数例如常见的有:-p允许直接在提示语后输入,-s以静默模式接收输入(适合密码等敏感信息),-t设置输入超时时间。

#!/bin/bash
# read命令示例
# 基本输入
echo -n "请输入您的姓名:"
read name
echo "你好,$name!"
# 简化写法
read -p "请输入年龄:" age
echo "年龄:${age}"
# 隐藏输入(用于密码)
read -s -p "请输入密码:" password
echo
echo "密码已接收"
# 超时设置
if read -t 5 -p "请在5秒内输入:" input; then
    echo "你输入了:${input}"
else
    echo "时间到!"
fi

9、 test命令

    Shell脚本中test命令有多种测试功能。该命令用于评估条件表达式并返回真假值,支持数值比较(如-eq判断相等、-lt判断小于)、文件测试(如-f检查普通文件是否存在)和字符串测试(如-n检查字符串非空)。同时也有方括号[ ]写法,它在功能上与test命令完全相同但语法更简洁。

  

#!/bin/bash
# test命令示例
a=5
b=10
# 数值比较
if test $a -eq $b; then
    echo "a等于b"
else
    echo "a不等于b"
fi
# 文件测试
if test -f "test.txt"; then
    echo "test.txt文件存在"
else
    echo "test.txt文件不存在"
fi
# 字符串测试
name="小王"
if test -n "${name}"; then
    echo "名字不为空"
fi
# 使用[]的等价写法(更常用)
if [ $a -lt $b ]; then
    echo "a小于b"
fi

三、Shell流程控制

1、if-else语句

  Shell中的if else语句和其他编程语言一样,是一种条件判断结构,用于根据条件执行不同的代码块。其基本语法如下:


if [ 条件判断 ]; then
    # 条件为真时执行的代码
else
    # 条件为假时执行的代码
fi

  if语句会检查条件表达式的返回值,如果返回0(真),则执行then后面的代码块;如果返回非0(假),则执行else后面的代码块。fi表示if语句的结束。其中还可以使用elif添加多个条件分支,条件判断可以使用test命令[ ]或双方括号[[ ]],也可以嵌套使用多个if语句。

#!/bin/bash
# if-else语句示例
read -p "请输入分数:" score
if [ $score -ge 90 ]; then
    echo "成绩优秀"
elif [ $score -ge 80 ]; then
    echo "成绩良好" 
elif [ $score -ge 70 ]; then
    echo "成绩中等"
elif [ $score -ge 60 ]; then
    echo "成绩及格"
else
    echo "成绩不及格"
fi

  if语句可以使用逻辑运算符连接多个条件进行多重条件判断。

#!/bin/bash
# 多重条件判断
read -p "请输入年龄:" age
read -p "是否是学生(y/n):" student
if [ $age -lt 18 ] && [ "${student}" = "y" ]; then
    echo "未成年学生"
elif [ $age -ge 18 ] && [ "${student}" = "y" ]; then
    echo "成年学生"
else
    echo "非学生"
fi

2、 for循环

  Shell中的for循环是一种重要的循环控制结构,用于遍历列表中的每个元素并重复执行相应的代码块。其基本语法如下:


for 变量 in 列表
do
    # 循环体 - 针对每个元素执行的命令
done

  for循环会依次从列表中取出每个元素,将其赋值给指定的变量,然后执行do和done之间的循环体代码,直到列表中的所有元素都被处理完毕。列表可以是显式给出的字符串序列,也可以是命令的执行结果、数字序列或者数组元素等。

#!/bin/bash
# for循环示例
# 遍历数字
for i in 1 2 3 4 5; do
    echo "当前数字:${i}"
done

# 遍历字符串列表
for course in "语文" "数学" "英语" ; do
    echo "学习科目:${course}"
done

# 遍历文件
echo ' '
for file in *.txt; do
    if [ -f "$file" ]; then
        echo "找到文件:${file}"
    fi
done

3、 while循环

  Shell中的while循环是一种条件控制循环结构,它会在条件为真时重复执行循环体内的代码块。其基本语法:

while [ 条件判断 ]
do
    # 循环体 - 条件为真时执行的命令
done

  while循环首先会检查条件表达式的返回值,如果条件为真(返回0),则执行do和done之间的循环体代码;执行完毕后会再次检查条件,如果条件仍然为真,则继续执行循环体,如此反复直到条件变为假(返回非0值)为止。这种循环结构适合处理像读取文件内容直到文件结束或者等待某个条件满足等场景。与for循环遍历固定列表不同,while循环的迭代次数通常在执行前无法确定,完全取决于条件的动态变化,因此在使用时需要注意避免创建无限循环,确保循环条件最终会变为假。

#!/bin/bash
# while循环示例
# 计数器
count=1
while [ $count -le 5 ]; do
    echo "计数:${count}"
    count=$((count + 1))
done
# 读取文件内容
echo " "
while read line; do
    echo "行内容:${line}"
done < example.txt

  今天我们就先学到这里,后面博主会继续在这篇文章中更新关与Shell编程后续的内容。

Logo

更多推荐