1.shell基础

Shell 本质就是 Linux 的命令解释器,我们敲的命令都是 Shell 翻译给内核的。常用:bash(默认)、sh、zsh。

第一行解释器声明,告诉系统我们在用什么执行

#! /bin/bash

一个简单的测试系统时间的脚本

3 种执行方式

  • 赋予权限直接执行:chmod +x test.sh && ./test.sh
  • 用 bash 调用:bash test.sh(不需要执行权限)
  • 加载到当前 shell:source test.sh 或 . test.sh(会影响当前终端环境)

几种常见的执行方式

2.变量:shell最基础的存储单元

1.自定义变量

自定义变量的定义,等号两边都不能有空格

name="xiaobai"        #等号两边都能有空格,否则会当成命令执行

变量命名规则

1. 不能包含字母、数字、下划线

2. 不能以数字开头

3. 区分大小写

在引用变量的过程中最好是加花括号防止后面跟着字母有歧义

name="xiaobai"

# 1.直接引用 
echo $name  # 输出:xiaobai

# 2. 加花括号能防止歧义
echo "我的名字是${name}abc"     #防止歧义$nameabc,没有这个变量

设置只读变量&&删除变量

# 只读变量且不能修改
readonly PI=3.14
PI=123     # 不能修改,只读会报错

#删除变量
unset name
echo $name #输出为空

2.环境变量

环境变量是系统预定义的变量,所有子进程都能继承和访问

常用的环境变量

变量名含义
PATH命令搜索路径,用冒号分隔
HOME当前用户的家目录
PWD当前工作目录
USER当前用户名
HOSTNAME主机名
SHELL当前使用的 Shell 解释器

自定义环境变量 

# 定义局部变量(只有当前shell能访问)
LOCAL_VAR="local"

#升级为环境变量(所有子shell都能访问)
export GLOBAL_VAR="global"

3.特殊变量

shell预定义的变量,获取脚本运行信息

#!/bin/bash
echo "脚本名称:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "总参数个数:$#"
echo "所有参数(整体):$*"
echo "所有参数(分开):$@"
echo "上一条命令退出码:$?"
echo "当前脚本进程ID:$$"

运行后的效果

重点:$*和$@的区别

只有在用双引号包裹时,它们的区别才会体现:

  • "$*":把所有参数当成一个整体
  • "$@":把所有参数当成独立的个体

下面示例

#!/bin/bash
echo "--- 遍历\$* ---"
for arg in "$*"; do
  echo $arg
done

echo "--- 遍历\$@ ---"
for arg in "$@"; do
  echo $arg
done

最后执行的情况遍历的此时不同,第一个只循环一次,第二个循环3次

最常用的 $?

含义:是上一条命令的退出状态码

规则:0表示执行成功,非0表示失败

主要用于判断上一条命令是否执行成功

ls /nonexistent  # 访问一个不存在的目录
echo $?  # 输出:2(表示失败)

ls /home
echo $?  # 输出:0(表示成功)

最后执行情况,明显2是失败的没有这个目录,而 /home是真实存在的

3.输入输出管道符

1.输入命令 echo:最常用的输出命令

# 普通输出 
echo "Hello World"

# 输出换行(默认会换行)
echo -n "1234"  # -m末尾取消换行

# 输出转义字符
echo -e "第一行\n第二行\t制表符"

printf:格式化输出        与c语言的printf用法一致,适合输出规整的表格

printf "姓名:%s,年龄:%d\n" "xiaobai" 20
printf "姓名:%s,年龄:%d\n" "小白" 25

2. 输入命令:read

从终端中读取用户输入

# 基本用法
read name
echo "你输入的名字是:$name"

# 带提示的输入(-p)
read -p "请输入你的年龄是" age
echo "你的年龄是:" $age

# 隐藏输入(-s)
read -s -p "请输入密码" password
echo -e "\n输入密码完成"

3.重定向

Linux中一切皆文件,每个进程默认打开3个文件描述符

文件描述符名称含义
0标准输入 (stdin)从键盘输入
1标准输出 (stdout)输出到终端
2标准错误 (stderr)错误信息输出到终端

重定向就是把这些默认输入输出改到其他地方

常用的重定向语法

语法含义
命令 > 文件把标准输出覆盖写入文件
命令 >> 文件把标准输出追加写入文件
命令 2> 文件把标准错误覆盖写入文件
命令 2>> 文件把标准错误追加写入文件
命令 &> 文件把标准输出和标准错误一起覆盖写入文件
命令 &>> 文件把标准输出和标准错误一起追加写入文件
命令 < 文件从文件读取输入
# 将ls的结果输入到file.txt(覆盖)
ls home > file.txt

# 追加写入
echo "内容" >> file.txt

# 把错误信息写入error.log
ls /nonexistent 2> error.log

# 把所有的输出都不显示
ls /home &>/dev/null

4.管道符

将前一个命令的标准输出,作为后一个命令的标准输入

# 统计当前目录下的文件个数
ls | wc -l

# 过滤包含nginx的进程
ps -ef | grep nginx

# 提取磁盘使用率
df -h / | grep / | awk '{print $5}'

注意管道两边都是在子shell中执行的,所以管道内变量不会影响外部

count=0
ls | while read line; do
    count=$((count+1))
done
echo $count
# 输出:0(因为while在子Shell中执行,外部count没有被修改)

4.条件判断 if/test/[]

shell中所有的条件判断,本质都是test命令,而[]是test的别名,基本语法

1.基本语法

# 写法1:用test命令
if test 条件; then
  命令
fi

# 写法2:用[ ](推荐,更常用)条件两边必须有空格
if [ 条件 ]; then
  命令
elif [ 条件 ]; then
  命令
else
  命令
fi
# if也能用(())作为条件,例如

if (( num == 1 )); then
  echo "数字为1"
else
  echo "数字不为1"
fi

 2.三类常用判断

(1)文件目录判断

符号含义
-f 文件判断是否是普通文件
-d 目录判断是否是目录
-e 文件/目录判断是否存在
-r 文件判断是否可读
-w 文件判断是否可写
-x 文件判断是否可执行
-s 文件判断文件是否非空(大小大于 0)

示例:判断是目录还是文件shell脚本

file="file.txt"
if [ -f "$file" ]; then
    echo "$file是普通文件"
elif [ -d "$file" ]; then
    echo "$file是目录"
else
    echo "$file不存在"
fi

(2)数值大小比较

符号含义
-eq等于(equal)
-ne不等于(not equal)
-gt大于(greater than)
-lt小于(less than)
-ge大于等于(greater or equal)
-le小于等于(less or equal)

大于gt、小于lt、等于eq、不等于ne、大于等于ge等,记住前4个,以及首字母就可

read -p "请输入年龄" age;
if [ $age -lt 18 ]; then
    echo "未成年"
else
    echo "成年"
fi

(3)字符串判断

符号含义
str1 = str2字符串相等
str1 != str2字符串不相等
-z str字符串为空(长度为 0)
-n str字符串非空

示例

name="zhangsan"
if [ -z "$name" ]; then
    echo "名字不能为空"
elif [ "$name" != "zhangsan" ]    # 两边一定要空格
    echo "名字不为张三"
else
    echo "你是张三"
fi

3.拓展[[]](bash特有)

[[ ]]是 bash 对[ ]的增强,支持更多功能:

  • 支持正则匹配
  • 逻辑运算符&&和||不需要转义
  • 字符串比较不需要加双引号(不会因为空格报错)

示例

# 正则匹配;判断字符串是否是以a开头
str="apple"
if[[ $str = ~^a.* ]]; then
    echo "字符串以a开头"
fi

# 逻辑运算符更加简洁
if [[ $a -gt 10 && $b -lt 20 ]]; then
    echo "条件满足"
fi

5.循环结构:for / while / until

1.for循环(最常用)

示例1:遍历列表

# 遍历数字列表
for i in 1 2 3 4 5; do
    echo "数字:$i"
done

# 遍历字符串列表
for name in zhangsan lisi wangwu zhouliu; do
    echo "名字:$"
done

# 遍历连续数字
for i in {1..10};do
    echo $i
done

# 带步长的连续数字:{开始..结束..步长}
for i in {1..10..2}; do
    echo $i # 输出:1 3 5 7 9
done

示例2:遍历文件

遍历当前目录下的所有txt文件
for file in *.txt; do
    echo "处理文件:$file"
done

示例3:c语言风格for循环

for((i=0; i<10; i++)); do
    echo $i
done

2.while循环

当条件为真的时候执行循环

i=1
while [ $i -le 5 ]; do
    echo $i
    i=$((i+1))
done

在算数计算的时候一般要用双括号,里面要进行的是数学计算不是字符串操作

常用场景:逐一进行文件读取

# 逐行读取file.txt内容
while read line; do
    echo "行内容:$line"
done < file.txt

3.until循环

和while相反,当条件为假的时候执行循环

i=1
util [ $i -gt 5 ]; do
    echo $i
    i=$((i+1))
done

4.循环控制

break:跳出整个循环

coutinue:跳过本次循环,继续下一次

for i in {1..10}; do
    if [ $q -eq 5 ]; then
        continue    # 跳过i=5
    fi
    if [ $q -eq 8 ]; then
        break       # 跳出循环,i=8后面的都不执行
    fi
    echo $i
done

6.逻辑预算符

符号含义用法说明
&&逻辑与(短路与)cmd1 && cmd2仅当cmd1成功(退出码0)时,才执行cmd2
``逻辑或(短路或)`cmd1cmd2`仅当cmd1失败(退出码≠0)时,才执行cmd2
!逻辑非(取反)! cmd反转cmd的退出状态码(成功变失败,失败变成功)

示例

# 如果文件存在,就删除他
[ -f test.txt ] && rm test.txt

# 如果目录不存在,就创建他
[ -d test_dir ] || mkdir test_dir

# 组合使用
[ -f test.txt ] && echo "文件存在" || echo "文件不存在"

7. 函数封装复用

1.函数定义与调用

hellow() {
    echo "hello"
}

# 直接调用函数
hello

2.函数参数的传递

greet() {
    echo "参数一 $1"
    echo "参数二 $2"
    echo "所有参数 $#"
}

调用参数传递
greep "zzx" 40

3.函数返回值

shel脚本只能返回0~255之间的整数

返回值用return语句,不写默认返回最后一条命令的退出嘛

调用者通过$?获取返回值

# 计算两个数的和
add() {
    sum=$(( $1 + $2 ))
    return $sum
}

add 10 20
echo "和是:$?" 

若返回字符串后者大于255的数字,不能用return,应该用echo输出,然后用$()捕获,例如一下形式

8.脚本调试

命令行调试

# 执行脚本时,打印每一条执行的命令
sh -x demo.sh

其他有用的 set 选项

  • set -e:脚本中任何命令执行失败,立即退出脚本
  • set -u:使用未定义的变量时,脚本报错并退出
  • set -o pipefail:管道中任何命令失败,整个管道命令失败

9.简单脚本练习

1.接收两个参数,输出和、差、积、商

更加完善健壮性

#!/bin/bash

# 四则运算函数:用法 sum 数字1 数字2 运算符
sum() {
  # 先检查参数数量是否正确
  if [ $# -ne 3 ]; then
    echo "用法错误!正确用法:$0 数字1 数字2 运算符"
    echo "支持的运算符:+ - * /"
    return 1
  fi

  # 用 [[ ]] 做字符串比较,更安全,不需要转义特殊字符
  if [[ $3 == "+" ]]; then
    echo $(( $1 + $2 ))
  elif [[ $3 == "-" ]]; then
    echo $(( $1 - $2 ))  # 修正:减法逻辑
  elif [[ $3 == "*" ]]; then
    echo $(( $1 * $2 ))
  elif [[ $3 == "/" ]]; then
    # 整数除法,添加除数为0的检查
    if [ $2 -eq 0 ]; then
      echo "错误:除数不能为0"
      return 1
    fi
    echo $(( $1 / $2 ))
  else
    echo "错误:请使用四则运算符 + - * /"
    return 1
  fi
}

# 测试调用示例(替换成你自己的参数即可)
echo "10 + 5 = $(sum 10 5 +)"
echo "10 - 5 = $(sum 10 5 -)"
echo "10 * 5 = $(sum 10 5 *)"
echo "10 / 5 = $(sum 10 5 /)"

2.判断数字为奇数还是偶数

更加完善健壮性

#!/bin/bash

# 1. 提示用户输入数字
read -p "请输入一个整数: " num

# 2. 验证输入是否为有效整数(支持正负数和0)
if [[ ! $num =~ ^-?[0-9]+$ ]]; then
  echo "错误:请输入有效的整数!"
  exit 1
fi

# 3. 用取模运算判断奇偶(除以2看余数)
if (( num % 2 == 0 )); then
  echo "$num 是偶数"
else
  echo "$num 是奇数"
fi

3.打印/var/log目录下一.log结尾的文件,打印文件名

IFS= read -r line 是 Shell 中逐行读取文件的「黄金标准写法」,核心目的是100% 原样保留文件中的每一行内容,避免 Shell 自动 “篡改” 数据

写入临时文件的方式读取文件

#!/bin/bash

# 定义临时文件路径(脚本所在目录)
temp_file="$(dirname "$0")/file.txt"

# 把ls结果写入临时文件
ls /var/log > "$temp_file"

# 标准read写法,原样保留所有字符
while IFS= read -r line; do
  echo "行内容:$line"
done < "$temp_file"

# 脚本结束自动删除临时文件
rm -f "$temp_file"

4.编写一个函数,接收一个文件名作为参数,判断该文件是否存在,如果存在则打印文件大小

健壮型代码

#!/bin/bash

# 函数功能:检查文件是否存在,存在则打印大小
# 用法:check_file_size "文件名"
check_file_size() {
  # 1. 验证参数数量(必须传1个文件名)
  if [ $# -ne 1 ]; then
    echo "错误:请传入且仅传入1个文件名作为参数!"
    echo "用法示例:check_file_size \"/var/log/syslog\""
    return 1
  fi

  # 用local定义局部变量,避免污染全局环境
  local file="$1"

  # 2. 检查文件是否存在且是普通文件(-f)
  if [ -f "$file" ]; then
    # 获取文件大小(字节数)
    # Linux用 stat -c %s;macOS请改成 stat -f %z
    local size_bytes=$(stat -c %s "$file")
    
    # 获取人类可读大小(自动转换 KB/MB/GB)
    local size_human=$(du -h "$file" | cut -f1)

    echo "✅ 文件 \"$file\" 存在"
    echo "   大小(字节):$size_bytes"
    echo "   大小(可读):$size_human"
  else
    echo "❌ 文件 \"$file\" 不存在或不是普通文件"
    return 1
  fi
}

# ---------------- 测试示例 ----------------
echo "=== 测试1:存在的文件 ==="
check_file_size "/etc/passwd"

echo -e "\n=== 测试2:不存在的文件 ==="
check_file_size "/tmp/nonexistent_file.txt"

echo -e "\n=== 测试3:带空格的文件名(需用引号包裹) ==="
# 先创建一个测试文件
touch "my file.txt"
check_file_size "my file.txt"
# 清理测试文件
rm -f "my file.txt"

5.编写一个脚本将目录下所有的.txt文件都改为.bak

符号删除方向记忆技巧
#从字符串开头删键盘上 # 在 $ 的左边(开头)
%从字符串末尾删键盘上 % 在 $ 的右边(末尾)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐