【嵌入式Linux基础】练气期之Shell详解及Shell编程入门——入门Shell这一篇就够了

今天看了火哥的嵌入式Linux教程,收获很多,对Shell有了新的认识,做笔记!

一、Shell的核心概念

当你打开黑窗口,在/下输入rm -rf *(别真试啊)的时候,那么恭喜你,你已经在使用Shell了。
so,什么是 Shell呢?

1.Shell 的本质是命令行解释器

  • 理解Shell之前我们可以先了解两个概念: 解释型语言 和 编译型语言
    • 编译型语言:写好的代码需要先通过 “编译器” 翻译成操作系统能直接理解的机器码(二进制文件),之后才能运行。比如 CC++就是常见的编程型语言,当键入int a = 10但是忘记加分号,或者更加夸张的把入口函数打成了int mian时,无法通过编译更别谈运行了。虽然要求严格,但是却有更好的执行效率。

    • 解释型语言:写好的代码不需要提前编译,而是由 “解释器”逐行读取、逐行解析并执行。比如我们常见的pythonShell脚本就是解释型语言,在写代码过程中即使后面错误,但是前面正确的部分仍然可以执行。这类语言相对来说更加简单便捷,但执行效率不如上者。

  • Shell 就是 执行一条条命令的解释器,那么Shell脚本就是执行一堆命令咯。

2.Shell 有两种工作模式

  • 交互式模式:典型的你问我答模式,你给一条命令,我就执行一条,日常终端就是这种模式
  • 批处理模式:用户写入一系列的命令,解释器一次性执行,可以实现一些自动化任务。

3.Shell解释器有哪些?

其实Shell解释器有非常多的种类,比如最常见的,Windows下的 PowerShell 还有 Linux 最常见的bash,如果可以,键入

cat /etc/shells

在这里插入图片描述
这些都是解释器咯。并且通过echo $SHELL就可以查看你当前所用的解释器了。
在这里插入图片描述
其实,本质上不论哪种Shell,可以理解为一个解释器程序,而命令就是你传给它的参数,它会帮你执行这个命令。

4.内置命令和外部命令

  • 内置命令:内置命令是Shell解释器自身包含的命令,不需要依赖外部文件,执行时由 Shell 直接处理,不创建新的进程。例如cd、echo、export等等。可以

  • 外部命令:外部命令是独立于 Shell 的可执行程序,以文件形式存储在磁盘中(通常位于 /bin、/usr/bin、/usr/local/bin 等目录),执行时需要 Shell 创建新进程来运行。例如ls、cp、grep、python等。

  • 通过type可以区分内置命令和外部命令。
    在这里插入图片描述

  • 当一个命令为外部命令时,Shell会到对应的路径去查找对应的程序,找到了才执行,此处可以通过echo $PATH来查看Shell能查找的目录在这里插入图片描述
    同时也可以通过添加路径的方式让Shell发现你。

#例如我编写了一个hello.c,用于打印Hello,world! 
#编译后的可执行文件在/home/lsy/workspace下
gcc hello.c -o hello

#直接执行hello
hello #报错:command not found

#添加路径
export PATH=$PATH:/home/lsy/workspace

#打印查看
echo $PATH

#直接执行hello
hello

在这里插入图片描述

二、Shell 编程

1.第一个Shell脚本

  • 学习一门语言,我想没有比输出Hello,world!更让人兴奋的事情了,创建一个hello.sh(Shell脚本后缀为.sh)
#!/bin/bash
#首行,告诉系统使用bash解释器
echo "Hello,world!"

接下来,执行它!

#改变文件权限,可执行
chmod 777 hello.sh
#执行
./hello.sh

在这里插入图片描述

2.Shell变量

  • 定义变量,命名规则:

    • 只能包含 字母、数字、下划线(a-z、A-Z、0-9、_)
    • 不能以 数字开头(如 1name 是错误的,name1 正确)。
    • 区分大小写(Name 和 name 是两个不同的变量)。
    • 避免使用 Shell 关键字(如 if、for 等)。
  • 变量赋值

    • 基本赋值
    #变量名=值,等号左右无空格,值如果包含空格要用引号包裹
    name=Jack
    message='Hello World'
    text="go to bed"
    age=18 
    
    • 变量的引用:使用变量时,需在变量名前加$符号,或用${变量名} 明确边界(避免歧义)。
echo $name
echo ${name}
echo "My name is $name"
echo 'My name i $name' #单引号打印原字符

在这里插入图片描述

  • 将命令的结果赋值给变量
# var=`command`
# var=$(command)

var1='pwd'
var2=$(ls)

echo $var1
echo $var2

运行结果:
在这里插入图片描述

  • 变量分类
    • 局部变量:作用范围当前Shell会话,不影响子Shell或者其他进程。例如,在当前Shell,我定义一个变量aecho a
      在这里插入图片描述
      当我输入bash打开子Shell时,无法得到10
      在这里插入图片描述
      exit退出
    • 环境变量:作用范围全局有效,可被当前 Shell 及其启动的所有子 Shell(如脚本中调用的其他命令 / 脚本)继承。可以用export命令将局部变量升级为环境变量
      在这里插入图片描述
      此时进入子Shell
      在这里插入图片描述
    • 特殊变量
变量符号具体含义
$0当前脚本的文件名(若执行路径为 ./test.sh,则 $0 输出 ./test.sh
$n(n≥1)传递给脚本/函数的第 n 个参数(如 $1 是第1个参数,$3 是第3个参数)
$#传递给脚本/函数的 总参数个数(若传3个参数,$# 输出 3)
$*所有参数的集合(不保留参数原格式,将所有参数合并为一个字符串)
$@所有参数的集合(保留参数原格式,每个参数独立;双引号包裹时与 $* 差异明显)
$?上一个命令的 退出状态码(0 表示执行成功,非0表示执行失败)
$$当前 Shell 进程的 PID(脚本运行时,即脚本所在进程的 ID)

我们看一个例子:

#!/bin/bash

echo "===== 特殊参数演示 ====="
echo "1. 脚本文件名(\$0):$0"
echo "2. 第1个参数(\$1):$1"
echo "3. 第2个参数(\$2):$2"
echo "4. 第3个参数(\$3):$3"
echo "5. 参数总个数(\$#):$#"
echo "6. 所有参数(\$*):$*"
echo "7. 所有参数(\$@):$@"
echo "8. 上一条命令退出状态(\$?):$?"  # 这里打印的是“echo $?”本身的退出状态(成功为0)
echo "9. 当前进程ID(\$\$):$$"

运行结果
在这里插入图片描述
$@、$*差异后续描述。

  • 变量拼接
name="Jack is a"
#注意,变量名并不会被自动识别,用空格隔开或者加{}
info="$nameboy"#错误
info="${name}boy"
echo $info
info="$name boy"
echo $info

在这里插入图片描述

  • 变量运算
    方式 1:$(( 表达式 )):支持+(加)、-(减)、*(乘)、/(除,整数除法)、%(取余),表达式内变量可省略$。
a=10
b=3
# 加法
echo $((a + b))
# 减法
echo $((a - b))
# 乘法(无需转义*)
echo $((a * b)) 
# 除法(向下取整)
echo $((a / b)) 
# 取余
echo $((a % b)) 
# 表达式内可直接写数值
echo $((5 + 6 * 2)) 

结果:
在这里插入图片描述
方式 2:expr 命令

a=10
b=3
echo $(expr $a + $b)  # 输出:13(+两边必须有空格)
echo $(expr $a \* $b)  # 输出:30(*必须转义)
echo $(expr $a / $b)  # 输出:3

结果:
在这里插入图片描述
方式 3:let 命令

a=10
b=3
let c=a+b
echo $c 
let a+=2 
echo $a 

结果:
在这里插入图片描述

3.常见命令

命令作用说明核心语法/常用选项示例
echo输出文本、变量或命令结果echo [选项] 内容
- -n:输出后不换行;
- -e:解析转义字符(如\n换行、\t制表符)
echo "Hello $name"(输出变量)
echo -e "Line1\nLine2"(换行输出)
exit退出当前Shell/脚本,返回状态码exit [状态码](0表示成功,非0表示错误,范围0-255)exit 0(正常退出)
exit 1(异常退出,标识错误)
cd切换当前工作目录cd [目录路径]
特殊路径:~(家目录)、..(上级目录)、-(上一次目录)
cd /home(切换到绝对路径)
cd ..(切换到上级目录)
pwd显示当前工作目录的绝对路径pwdpwd(输出:/home/user/projects
read读取用户输入并存储到变量read [选项] 变量名
- -p:显示提示文本;
- -s:隐藏输入(如密码);
- -n 数字:限制输入字符数
read -p "输入姓名:" name(带提示读取)
read -s -p "密码:" pwd(隐藏输入)
export将局部变量升级为环境变量(子进程可继承)export 变量名=值(直接定义并导出)
export 变量名(先定义局部变量再导出)
export PATH=$PATH:~/bin(扩展命令路径)
age=20; export age
unset删除已定义的变量(局部或环境变量)unset 变量名name="Tom"; unset name(删除name变量)
type判断命令类型(内置/外部/别名)type 命令名type cd(输出:cd is a shell builtin
type ls(输出路径)
test/[ ]条件判断(文件/数值/字符串状态)test 条件[ 条件 ][ ]前后必须有空格)
常见条件:
- 文件:-f 路径(是否为普通文件)、-d 路径(是否为目录);
- 数值:a -eq b(a等于b)、a -gt b(a大于b);
- 字符串:"str1" == "str2"(相等)、-z "str"(是否为空)
[ -f "file.txt" ](判断文件是否存在)
[ $a -gt $b ](判断a是否大于b)

4.逻辑与和或

  • 与和或解析
command1 && command2
#对于逻辑与:
	#如果command1不成立 ,那么command2不会执行
	#如果command1成立,那么command2会执行
command1 || command2
#对于逻辑或
	#如果command1成立,那么command2就不会执行
	#如果command1不成立,那么command2会执行
  • 条件判断的两种方式
#使用test
test condition
#使用[]但是前后必须有空格
[ condition ]

#示例
a=1
b=2
#方式一
test $a -eq $b
#方式二
[ $a -eq $b ]
  • Shell 条件判断常用选项表

一、文件测试(检查文件/目录状态)

选项含义示例([ 选项 路径 ]
-e路径是否存在(文件/目录均可)[ -e "/home/file.txt" ]
-f路径是否为普通文件(非目录/链接)[ -f "data.log" ]
-d路径是否为目录[ -d "/var/log" ]
-r路径是否可读(当前用户权限)[ -r "config.ini" ]
-w路径是否可写(当前用户权限)[ -w "output.txt" ]
-x路径是否可执行(当前用户权限)[ -x "./script.sh" ]
-s文件是否非空(大小 > 0)[ -s "result.txt" ]
-L路径是否为符号链接[ -L "link_to_file" ]
-nt文件A是否比文件B新(修改时间)[ "fileA.txt" -nt "fileB.txt" ]
-ot文件A是否比文件B旧(修改时间)[ "fileA.txt" -ot "fileB.txt" ]

二、数值比较(仅用于整数,变量需为数值)

选项含义示例([ 数值1 选项 数值2 ]
-eq等于(equal)[ $a -eq $b ](判断变量a是否等于b)
-ne不等于(not equal)[ $count -ne 0 ](判断count是否不等于0)
-gt大于(greater than)[ $age -gt 18 ](判断age是否大于18)
-lt小于(less than)[ $num -lt 100 ](判断num是否小于100)
-ge大于等于(>=)[ $score -ge 60 ](判断score是否≥60)
-le小于等于(<=)[ $size -le 1024 ](判断size是否≤1024)

三、字符串比较(变量或文本)

选项/符号含义示例([ 字符串1 选项 字符串2 ]
==/=字符串相等(===的扩展,推荐==[ "$name" == "Alice" ]
!=字符串不相等[ "$status" != "success" ]
-n字符串长度非空(not zero)[ -n "$input" ](判断input是否有内容)
-z字符串长度为空(zero)[ -z "$empty_var" ](判断变量是否为空)
>字符串按字典序大于(需转义\>[ "apple" \> "banana" ](结果为假)
<字符串按字典序小于(需转义\<[ "apple" \< "banana" ](结果为真)

5.管道操作

  • |表示管道,它的作用是将左侧命令的****标准输出(stdout)直接作为右侧命令的****标准输入(stdin),实现 “数据流水线” 式的处理
#查看当前路径下所有.sh文件
ls | grep ".sh"
#统计hello.c行数
cat hello.c | wc -l
#找到当前的python进程
ps aux | grep "python"

看看结果:
在这里插入图片描述

  • 管道操作可以连用,但是尽量避免过度串联

6.if-else

  • 模板
if condition1
then
	statement1
elif condition2
then
	statement2
else
	statement3
fi
  • 二选一
file="data.txt"
if [ -f "$file" ]; then  # -f判断是否为普通文件
  echo "$file 存在"
else
  echo "$file 不存在"
fi

运行结果:

在这里插入图片描述

  • 多选一
read -p "请输入score:" score
if [ $score -ge 90 ]; then  # -ge:大于等于
  echo "优秀"
elif [ $score -ge 60 ]; then
  echo "及格"
else
  echo "不及格"
fi

运行结果:

在这里插入图片描述

7.case in 语句

  • 基本模板
case 变量名 in
  模式1)
    # 变量匹配模式1时执行的命令
    命令1
    命令2
    ;;  # 分支结束符(两个分号)
  模式2)
    # 变量匹配模式2时执行的命令
    命令3
    ;;
  模式3|模式4)  # 多个模式用|分隔(逻辑或)
    # 变量匹配模式3或模式4时执行的命令
    命令4
    ;;
  *)  # 通配符*表示“所有未匹配的情况”(默认分支)
    # 未匹配任何模式时执行的命令
    命令5
    ;;
esac  # case的反写,标识整个语句结束
  • 举个栗子
#!/bin/bash

read -p "请输入一个字符:" char

case $char in
  [a-z])  # 匹配小写字母
    echo "你输入了小写字母:$char"
    ;;
  [A-Z])  # 匹配大写字母
    echo "你输入了大写字母:$char"
    ;;
  [0-9])  # 匹配数字
    echo "你输入了数字:$char"
    ;;
  *)  # 其他字符(如符号、多字符)
    echo "你输入了特殊字符或字符串:$char"
    ;;
esac

结果:
在这里插入图片描述

  • case vs if-elif-else
    • 用case更合适的场景:
      1.变量的可能取值是固定的离散值(如选项 1/2/3、状态码 0/1/2)
      2.需要模式匹配(如文件名后缀、字符串前缀、字符范围)
      3.分支数量较多(超过 3 个时,case比if-elif-else更易读)
    • 用if-elif-else更合适的场景:
      需要数值比较(如> < >=)或复杂条件组合(如a>10 && b<20)。

8. for in 循环

Shell 脚本中,for in循环是遍历列表元素的常用结构,核心功能是 “依次取出列表中的每个值,赋值给变量,然后执行循环体命令”。

  • 基本模板
for 变量名 in 元素列表; do
  # 循环体:对当前变量执行的命令
  命令1
  命令2
done
  • 举例一
# 遍历水果列表
for fruit in apple banana "cherry tomato" date; do
  echo "当前水果:$fruit"
done

# 遍历数字1到5,方式一
for num in 1 2 3 4 5; do
  echo $num
done

# 遍历数字1到5,方式二
for num in {1..5}; do  # 用{1..5}表示1到5的连续数字,添
  echo $num
done

运行结果
在这里插入图片描述

  • 使用通配符
# 遍历当前目录的.sh文件,仅打印文件名
for sh_file in *.sh; do
  # 只处理真实存在的普通文件(排除无.sh文件时的"*.sh"字符串)
  [ -f "$sh_file" ] && echo "$sh_file"
done

结果:
在这里插入图片描述

  • $*$@区别
变量不加双引号时加双引号时
$*将所有参数以空格分隔合并为一个列表所有参数被合并成单个字符串(用 IFS 变量的第一个字符分隔,默认是空格)
$@$* 行为一致(按空格分割为列表)保留每个参数的独立性,每个参数作为单独的元素(即使参数包含空格,也会被当作一个整体)

让我们打印出来看看:

#!/bin/bash

echo "===== 用 \$* 遍历(不加双引号) ====="
for arg in $*; do
  echo "参数:$arg"
done

echo -e "\n===== 用 \$@ 遍历(不加双引号) ====="
for arg in $@; do
  echo "参数:$arg"
done

echo -e "\n===== 用 \"\$*\" 遍历(加双引号) ====="
for arg in "$*"; do
  echo "参数:$arg"
done

echo -e "\n===== 用 \"\$@\" 遍历(加双引号) ====="
for arg in "$@"; do
  echo "参数:$arg"
done

运行结果:
在这里插入图片描述

9.While循环

while循环是基于条件判断的循环结构—— 只要指定的条件为 “真”(命令退出状态码为 0),就会重复执行循环体中的命令。它适合处理 “需要满足某个条件才停止” 的场景(如等待用户输入正确值、读取文件直到末尾、无限循环等),比for in循环更灵活。

  • 基本模板
while 条件判断; do
  # 循环体:条件为真时执行的命令
  命令1
  命令2
  # (可选)修改条件相关的变量,避免无限循环
done
  • 简单示例
input=""  # 初始化输入变量
while [ "$input" != "yes" ]; do  # 条件:输入不是"yes"时循环
  read -p "请输入yes继续:" input
done

结果示例;
在这里插入图片描述

  • whilefor in
循环类型适用场景控制方式
while基于条件判断的循环(如 “满足 X 则停止”)依赖条件表达式的真假,需手动更新条件变量
for in遍历已知列表(如固定值、文件列表、参数)自动遍历列表元素,无需手动控制次数

10.函数

Shell 脚本中,函数用于将一组命令封装起来,实现代码复用和模块化。它可以接收参数、执行操作并返回结果,让脚本结构更清晰,尤其适合复杂脚本中重复逻辑的处理。

  • 定义方式,两种等价
# 方式1:function关键字 + 函数名 + { 命令 }
function 函数名 {
  # 函数体:要执行的命令
  命令1
  命令2
}

# 方式2:函数名() + { 命令 }
函数名() {
  # 函数体
  命令1
  命令2
}
  • 调用方式,无需括号,和其他语言不同
# 定义函数
say_hello() {
  echo "Hello, world!"
}

# 调用函数
say_hello 
  • 函数参数
    Shell 函数通过位置参数接收外部传入的值,与脚本的位置参数规则一致。($1 $2 ....
    例如:
#!/bin/bash

# 定义函数:计算两个数的和
add() {
  # 接收参数($1是第一个数,$2是第二个数)
  local a=$1  # local声明局部变量(仅函数内有效)
  local b=$2
  echo "$((a + b))"
}

# 调用函数并传递参数
result=$(add 3 5)  # 用$(...)捕获函数输出的结果
echo "3 + 5 = $result"

结果:
在这里插入图片描述

  • 函数返回值,返回值有两种

    • 退出状态码:用return 数字返回退出状态码(0-255,0 表示成功,非 0 表示错误),类似exit但不终止脚本;
      外部通过$?(上一条命令的退出状态)获取。
    • 计算结果:若需返回具体数值(如计算结果),需通过echo输出,外部用$(函数名)捕获。
    • 注意:return只能返回 0-255 的整数,无法返回字符串或大数值,因此复杂结果必须用echo输出。
  • 局部变量与全局变量

    • 全局变量:在函数外定义的变量,默认在函数内也可访问和修改;
    • 局部变量:在函数内用local声明的变量,仅在函数内部有效,不影响外部变量(推荐优先使用,避免污染全局)。

三、总结

其实Shell并不复杂,如果学习过C语言,那么这个语法应该很熟悉了,如果学习过python应该也能找到相似之处。

参考:野火Linux系列教学视频之“零基础入门”篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值