首页  /  Linux 进阶  /  正文

Shell 内置命令与字符串处理

Linux 进阶 2026-10-02📖 16 分钟👁 —
🐚 Linux 进阶 · Shell 编程第 2 / 10 页12345678910📖 完整导航 →

一、几个简单的内置 shell 命令

前面的内容主要是记住就行,这里开始注重逻辑思维,写稍微复杂点的脚本,本阶段内容都很有用。

1. echo

-n 不换行输出,-e 解析字符串的特殊符号(\n 换行、\r 回车、\t 制表符、\b 退格符)。

echo 命令通过 ; 隔开默认有换行:

ubuntu@VM-0-9-ubuntu:~$ echo "111"; echo "222"
111
222

第一个 echo 加入 -n 会取消换行:

ubuntu@VM-0-9-ubuntu:~$ echo -n "111"; echo "222"
111222

后面 echo 加:

ubuntu@VM-0-9-ubuntu:~$ echo "111"; echo -n "222"
111
222ubuntu@VM-0-9-ubuntu:~$ 

使用 -e 换行:

ubuntu@VM-0-9-ubuntu:~$ echo -e "111\n222"
111
222
ubuntu@VM-0-9-ubuntu:~$ echo "111\n222"
111\n222

顺便记一下,ubuntu 的 /bin/sh 是 dash,dash 的 echo 默认就解析转义,所以同样的写法在 sh 和 bash 下结果可能不一样,写脚本时想要稳定就用 printf。

2. printf

和 c 一样的:

ubuntu@VM-0-9-ubuntu:~$ printf "11122\n33"
11122
33ubuntu@VM-0-9-ubuntu:~$ printf "11122\n33\n"
11122
33
ubuntu@VM-0-9-ubuntu:~$ 

3. eval

执行多个命令:

ubuntu@VM-0-9-ubuntu:~$ eval ls ; cd /etc
nohup.out  special.sh
ubuntu@VM-0-9-ubuntu:/etc$ 

eval 的作用是把参数扫描两次,第一次做变量替换和命令展开,第二次执行展开后的命令,上面 eval ls 里没有变量和特殊符号,eval 展开后还是 ls,跟直接敲 ls 没区别。

只有当命令里嵌了变量、反引号、或者类似 echo $(pwd) 这种需要二次解析的情况,eval 才显作用,正常脚本里很少用到。

4. exec

不创建子进程,执行后续命令且执行完毕自动 exit:

ubuntu@VM-0-9-ubuntu:/etc$ su - root
Password: 
root@VM-0-9-ubuntu:~# exec date
Tue Jul 21 03:43:14 PM CST 2026
ubuntu@VM-0-9-ubuntu:/etc$ 

二、变量字串

1. 语法

name="yuchao" 这样先赋值,然后:

2. 统计字符串长度

运维需要频繁对文本进行处理,文本存储在文件、变量等复杂形式中。

Shell 变量截取字符串有 2 种:指定位置、指定字符串。

ubuntu@VM-0-9-ubuntu:~/shell$ cat test.txt
server-test
11111111112312313
diuhkjnfdnxcgtyhnm17age

# 统计行数
ubuntu@VM-0-9-ubuntu:~/shell$ cat test.txt | wc -l
3

# 找出最长的一行并输出个数
ubuntu@VM-0-9-ubuntu:~/shell$ cat test.txt | wc -L
23

# 数值计算expr length [字符串]统计长度
ubuntu@VM-0-9-ubuntu:~/shell$ echo $name
yuchao180
ubuntu@VM-0-9-ubuntu:~/shell$ expr length $name 
9

# awk对一整行统计长度
ubuntu@VM-0-9-ubuntu:~/shell$ echo $name | awk '{print length($0)}'
9

还有很多方法,但是最快的是 echo ${#name}。

for 循环简单介绍一下:

# for 变量名 in 序列1~100
# for number in {1..100}
# do
# 	事情
# done
for num in {1..100};do echo $num;done
1
2
...
100

seq 是生成序列的命令:

ubuntu@VM-0-9-ubuntu:~/shell$ seq 3
1
2
3

-s 指定分隔符(默认换行):

ubuntu@VM-0-9-ubuntu:~/shell$ seq -s ":" 3
1:2:3
# 结合for命令
ubuntu@VM-0-9-ubuntu:~/shell$ for n in {1..3};do str1=`seq -s ":" 10`;echo $str1;done
1:2:3:4:5:6:7:8:9:10
1:2:3:4:5:6:7:8:9:10
1:2:3:4:5:6:7:8:9:10

# 结合time命令,计算统计字符的时间
time for n in {1..10000};do char=`seq -s "666" 100`;echo ${#char} &>/dev/null;done

real	0m28.473s # 实际运行时间
user	0m9.382s # 用户态执行时间
sys	0m19.627s # 内核态执行时间
# 由此可知${#变量}计算时间为28s

# 使用管道符效率一定很低
time for n in {1..10000};do char=`seq -s "666" 100`;echo ${char}|wc -l &>/dev/null;done

real	0m51.802s
user	0m19.627s
sys	0m41.030s

# 使用expr命令length函数统计
time for n in {1..10000};do char=`seq -s "666" 100`;echo ${char}|expr length ${char} &>/dev/null;done

real	0m50.750s
user	0m19.367s
sys	0m41.355s

# awk处理
time for n in {1..10000};do char=`seq -s "666" 100`;echo ${char}|echo $char|awk '{print length($0)}' &>/dev/null;done

real	0m55.953s
user	0m29.133s
sys	1m7.104s

Shell 编程使用 linux 内置的函数、命令效率最高。

反引号把命令执行结果当作字符串(不是单引号),要解析命令就用反引号。

3. 重定向到 /dev/null

/dev/null 是 Linux 里的一个特殊文件,所有写进去的内容都会被直接丢弃。它的作用是让输出“消失”,而不是像 rm 那样删除文件。

Shell 脚本里经常需要控制信息的流向,主要涉及三种标准流:

重定向符号 > 默认操作的是标准输出,所以 > file 等价于 1> file;标准错误要单独写 2> file,两个都要就写 &> file(或者 >file 2>&1)。追加用 >>,不覆盖原内容。

所以脚本里常见的 &>/dev/null 就是把正常输出和报错一起扔进黑洞,屏幕上干干净净。


三、详解字符串

1. 正则会区分大小写

2. 删除匹配的子串

# 索引删除匹配
ubuntu@VM-0-9-ubuntu:~$ name="I ma chaoge"
ubuntu@VM-0-9-ubuntu:~$ echo ${name:2:2}
am
ubuntu@VM-0-9-ubuntu:~$ echo ${name:2:4}
am c


# 从开头匹配字符删除
ubuntu@VM-0-9-ubuntu:~$ name2="abcABC123ABCabc"
ubuntu@VM-0-9-ubuntu:~$ echo ${name2#a*c}
ABC123ABCabc
# 开头必须是a
# 匹配到了最短的a到c,也就是开头的abc部分
ubuntu@VM-0-9-ubuntu:~$ echo ${name2##a*c}

ubuntu@VM-0-9-ubuntu:~$ 
# 匹配到了开头的a和结尾的c,全删除

# 从结尾匹配字符删除
ubuntu@VM-0-9-ubuntu:~$ echo ${name2%a*c}
abcABC123ABC
# 结尾必须是c
ubuntu@VM-0-9-ubuntu:~$ echo ${name2%%a*c}

ubuntu@VM-0-9-ubuntu:~$ 

ubuntu@VM-0-9-ubuntu:~$ echo ${name2%%a*C}
abcABC123ABCabc
# 结尾没有大写C,不会删除

3. 替换字符串

# 替换字串(不修改原变量)
ubuntu@VM-0-9-ubuntu:~$ echo ${str1/man/boy}
Hello,boy,i am your brother
# 替换匹配到的第一个
ubuntu@VM-0-9-ubuntu:~$ echo ${str1//o/O}
HellO,man,i am yOur brOther
# 如上

四、删除文件名案例

1. 准备数据

在 sub_str 目录里用 touch chaochao_{1..5}_finished.jpg 和 .png 造出 10 个测试文件

需求:去掉上图所有文件的 _finished 后缀。

文件直接赋给变量,获取的只是文件名,没有内容。

修改变量和修改文件名无关。

2. 单个文件改名的思路

  1. 不使用 shell 语法:用 mv 一个个改
  2. 利用变量字符串语法:
f=chaochao_1_finished.png,echo ${f//_finished/} 得到 chaochao_1.png,但变量和磁盘上的文件都没动

替换语法并不改变原有文件名,图片仅仅是演示。

  1. 利用变量反引号:
ubuntu@VM-0-9-ubuntu:~/sub_str$ echo date
date
ubuntu@VM-0-9-ubuntu:~/sub_str$ echo `date`
Tue Jul 21 05:38:33 PM CST 2026

反引号可以取到命令执行的结果。

ubuntu@VM-0-9-ubuntu:~/sub_str$ echo $f
chaochao_1_finished.jpg
ubuntu@VM-0-9-ubuntu:~/sub_str$ echo ${f/_finished/}
chaochao_1.jpg
ubuntu@VM-0-9-ubuntu:~/sub_str$ ll
total 8
drwxrwxr-x 2 ubuntu ubuntu 4096 Jul 21 17:32 ./
drwxr-x--- 7 ubuntu ubuntu 4096 Jul 21 17:31 ../
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_1_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_1_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_2_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_2_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_3_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_3_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_4_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_4_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_5_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_5_finished.png
ubuntu@VM-0-9-ubuntu:~/sub_str$ mv $f `echo ${f//_finished/}`
ubuntu@VM-0-9-ubuntu:~/sub_str$ ll
total 8
drwxrwxr-x 2 ubuntu ubuntu 4096 Jul 21 17:47 ./
drwxr-x--- 7 ubuntu ubuntu 4096 Jul 21 17:31 ../
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_1_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_1.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_2_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_2_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_3_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_3_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_4_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_4_finished.png
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_5_finished.jpg
-rw-rw-r-- 1 ubuntu ubuntu    0 Jul 21 17:32 chaochao_5_finished.png
ubuntu@VM-0-9-ubuntu:~/sub_str$ echo $f
chaochao_1_finished.jpg

这里修改变量的操作成功了,不代表修改变量就是修改了文件,二者是不一样的。这个操作是这样的:

$f 的结果是文件名,而 ${f//_finished/} 是 chaochao_1.jpg,相当于在当前目录执行:

mv chaochao_1_finished.jpg chaochao_1.jpg

最关键的是最后变量没变,只是作为修改文件的字符。

f="chaochao_1_finished.jpg" 是变量赋值,${f//_finished/} 是字符串替换,echo 输出新字符串,这三步都发生在内存里;一直到 mv 旧名 新名 才真正动磁盘上的文件。

3. 批量替换文件名(日志切割经常使用)

需求:去掉 jpg 文件的 _finished。

# 找出需要替换的文件
ubuntu@VM-0-9-ubuntu:~/sub_str$ ls *.jpg
chaochao_1.jpg           chaochao_3_finished.jpg  chaochao_5_finished.jpg
chaochao_2_finished.jpg  chaochao_4_finished.jpg
ubuntu@VM-0-9-ubuntu:~/sub_str$ ls *fin*.jpg
chaochao_2_finished.jpg  chaochao_3_finished.jpg  chaochao_4_finished.jpg  chaochao_5_finished.jpg

ubuntu@VM-0-9-ubuntu:~/sub_str$ for file in `ls *fin*.jpg`;do echo $file;done
chaochao_2_finished.jpg
chaochao_3_finished.jpg
chaochao_4_finished.jpg
chaochao_5_finished.jpg

# 替换
ubuntu@VM-0-9-ubuntu:~/sub_str$ for file in `ls *fin*.jpg`;do mv $file `echo ${file//_finished/}`;done
ubuntu@VM-0-9-ubuntu:~/sub_str$ ls *.jpg
chaochao_1.jpg  chaochao_2.jpg  chaochao_3.jpg  chaochao_4.jpg  chaochao_5.jpg

这一页其实就两件事:几个内置命令先把手上顺手了,然后记住 ${var//旧/新} 这种字符串替换根本不碰磁盘, 真想改文件名得靠 mv,日志切割那种批量改名就是这么拼出来的。