shell概论
shell
shell的中文翻译是外壳
,是一种计算机操作系统中的用户界面,提供了与操作系统内核进行交互的途径。它是用户与操作系统之间的中间层,允许用户执行命令、运行程序和管理文件系统等操作。
总的来说shell是一个命令解释器,比如Linux中的ls、cd、pwd等等命令都属于shell命令。通过接受用户输入的Shell命令来与操作系统进行交互
常见的shell有:
Linux、unix、macOS:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- Korn Shell(/usr/bin/ksh)
- Z Shell(/usr/bin/zsh)
Windows:
- Command Prompt
- PowerShell
shell脚本
Shell脚本是就是一种由Shell命令组成的文本文件,我们可以编写自己的shell脚本来实现一定的功能,以便于后来复用。Linxu中一般bash用的比较多。
脚本文件开头需要指定脚本使用的解释器:
#! /bin/bash
#!
被称为"shebang"或"hashbang",它告诉操作系统该脚本应该由哪个解释器来执行。如果写的是python脚本就在文件开头写入#! /usr/bin/env python
shell脚本的运行方式、
例如:新建一个test.sh文件写入:echo "Hello World"
用解释器执行:
[root@VM-8-17-centos Linux-Study] bash test.sh Hello Longlong!
作为可执行文件执行:如果直接执行:
[root@VM-8-17-centos Linux-Study] ./test.sh bash: ./test.sh: Permission denied
可以看到是拒绝执行的,我们需要使他有可执行权限:
使用chmod +x
命令[root@VM-8-17-centos Linux-Study] chmod +x test.sh [root@VM-8-17-centos Linux-Study] ./test.sh Hello Longlong!
shell注释
- 单行注释:
#这是单行注释
- 多行注释:
:<<anyword 第一行注释 第二行注释 第三行注释 anyword
anyword 可以替换任意值,甚至中文都可以, 只要上下对应上就行
shell变量
定义:
variable_name=value
注意,在等号两边不能有空格。有空格的话系统会认为空格之后该命令就结束了,然后会将value当成一个命令
例如:
x= hello #报错:hello: command not found
bash中默认都是字符串类型的,也可以这么定义:variable_name='value'
或者variable_name="value"
以上三种并无大区别
但是当你想要变量的值中含有空格的时候,你就需要加引号了:
x="hello word"
引用变量:
用$
来引用变量的值
x="hello word"
echo $x # 输出hello word
echo ${x} # 输出hello word
echo ${x}6666 # hello word6666
虽然说不带花括号也行,但一般还是带上,可以识别你真正的变量,比如第三个例子
只读变量
可以使用readonly或者declare -r关键字将变量设置为只读,这样一旦赋值后就无法再修改。
例:
x=hello
readonly x
declare -r x
删除变量
使用关键字:unset
可以删除变量
其实所谓删除变量,就是将变量置为空,因为bash中严格来说变量是不用定义的.就比如直接在命令行中输出:echo $变量名
,也不会报错, 只会输出空行。所以这个unset关键词等价于将x置为空x=''
x=111
echo ${x} #输出111
unset x
echo ${x}#输出空一行
x=111
x= #或者x=""、x=''都可以
echo $x #输出空一行
环境变量(全局变量)
环境变量是一种特殊类型的变量,它们由操作系统或Shell设置,如果被设为环境变量,那么它将在当前Shell会话以及所有由该Shell启动的子进程中可用。可以使用export
命令或者declare -x 变量名
将普通变量提升为环境变量。
temp='11'
export xx #或者 declare -x temp
相反的,有全局变量就有局部变量,脚本内以默认形式自定义的变量默认就是局部变量.
同时也可以将环境变量变为局部变量
export temp=11
declare +x name
字符串变量
- 单引号和双引号的区别:
- 单引号中的内容会原样输出,不会执行、不会取变量;
- 双引号中的内容可以执行、可以取变量;
例:temp='这里是变量' echo 'hello,$temp,\"11\"' #输出hello,$temp,\"11\" echo "hello,$temp,\"11\"" #输出hello,这里是变量,"11"
- 获取字符串长度以及字串
- 获取长度:
${#变量名}
- 提取字串:
${变量名:0:5}
- 获取长度:
特殊变量:
Bash还提供了一些特殊的预定义变量
$0
: 代表当前脚本的名称(或命令)。在脚本中,它用于获取当前脚本的名称。
$1, $2, $3, ...:
代表脚本的位置参数,即脚本执行时传递的参数。$1
表示第一个参数,$2
表示第二个参数,以此类推。
$*
: 由所有的位置参数构成的用空格隔开的字符串。它表示所有传递给脚本的参数。
$@
: 每个位置参数分别用双引号括起来的字符串。它表示所有传递给脚本的参数,每个参数都以双引号括起来。
$#
: 代表位置参数的个数,即传递给脚本的参数个数。
$$
: 代表当前脚本的进程ID(PID)。在脚本中,它用于获取当前脚本的进程ID。
$?
: 代表上一条命令的退出状态(exit code)。0表示命令成功执行,其他值表示命令执行出错。
$!
: 代表后台运行的最后一个进程的进程ID。在脚本中,它用于获取后台进程的PID。
$IFS
: 代表输入字段分隔符(Internal Field Separator),用于指定字段(单词)之间的分隔符,默认为包含空格、制表符和换行符的字符串。
$(command)
:返回command
这条命令的stdout(可嵌套)
\command\
:返回command
这条命令的stdout(不可嵌套)
例:
#! /bin/bash
echo "文件名:"$0
echo "第一个参数:"$1
echo "第二个参数:"$2
echo "第三个参数:"$3
echo "第四个参数:"$4
echo "所有参数1:" $*
echo "所有参数2:" $@
echo "参数个数:" $#
echo "当前脚本的pid:" $$
echo "上一条命令的退出状态:"$?
echo "后台运行的最后一个进程的PID:"$!
echo "输入的字段分隔符":$IFS
然后在终端执行:
[root@VM-8-17-centos Linux-Study] ./test.sh one two three four
输出:
文件名:./test.sh
第一个参数:one
第二个参数:two
第三个参数:three
第四个参数:four
所有参数1: one two three four
所有参数2: one two three four
参数个数: 4
当前脚本的pid: 2672280
上一条命令的退出状态:0
后台运行的最后一个进程的PID:
输入的字段分隔符:
$(command)
:返回command
这条命令的stdout(可嵌套)
\command\
:返回command
这条命令的stdout(不可嵌套)
关于这两个:
就是例如: ls
这个命令,会有stdout:就是列出当前目录下的所而 \ls\
或者$(command)
就会将这个stdout给获取了,之后你可以将这个赋给其他变量值或者写道一个文件里,再或者可以直接输出,
例:
temp=$(ls) #或者是`ls`
echo $temp
输出
test.sh
而这个能不能嵌套就是指的是$(command)
比如:
listing=$(ls -l $(cat filenames.txt))
假设有一个名为 filenames.txt 的文本文件,其中包含一系列文件名,每个文件名占据一行。命令的目标是读取 filenames.txt 文件中的文件名,并将每个文件名作为参数传递给 ls -l 命令,获取这些文件的详细列表信息,并将结果存储在 listing 变量中。
但是如果用``的话就得用转义符,而不能直接嵌套
listing=`ls -l \`cat filenames.txt\``
关于更多这两个命令的区别参考What is the difference between $(command) and command
in shell programming?
shell数组
定义
array=(变量1 变量2 变量3)
变量之间用空格隔开
也可以直接赋值
array[0]=变量1
array[1]=变量2
array[2]=变量3
读取整个数组
${array[@]} # 第一种写法
${array[*]} # 第二种写法
数组长度
使用#
关键符号
${#array[@]} # 第一种写法
${#array[*]} # 第二种写法
shell数值运算和字符串处理—expr命令
expr
是一个用于数值运算和字符串处理的Bash内置命令。它可以执行加法、减法、乘法、除法以及字符串的比较和操作。expr 命令的一般语法如下:
expr 表达式
说明:
- 用空格隔开每一项
- 用反斜杠放在shell特定的字符(如乘法符号
*
)前面(发现表达式运行错误时,可以试试转义) - 对包含空格和其他特殊字符的字符串要用引号括起来
expr会在stdout中输出结果。如果为逻辑关系表达式,则结果为真时,stdout输出1,否则输出0。 - expr的exit code:如果为逻辑关系表达式,则结果为真时,exit code为0,否则为1。
- 在数值运算时,expr 命令支持整数运算,而不支持浮点数运算。
字符串表达式
length STRING
:返回STRING的长度index STRING CHARSET
:
查找任意单个字符在STRING中最前面的字符位置,下标从1开始。如果在STRING中完全不存在CHARSET中的字符,则返回0。注意这里CHARSET可以不止写一个字符,可以写一个字符串,这样会从第一个字符串开始寻找。直到找到substr STRING POSITION LENGTH
:
返回STRING字符串中从POSITION开始,长度最大为LENGTH的子串。如果POSITION或LENGTH为负数,0或非数值,则返回空字符串。例如:
string="Hello" echo $(expr length "$string") # 获取字符串长度,结果为 5 echo $(expr substr "$string" 2 4) # 获取字符串从第二个开始,长度为4的子串,结果为 "ello" echo $(expr index "$string" "l") # 查找字符 "l" 在字符串中的位置,结果为 3 #或者用`` echo `expr length "$string"` # 获取字符串长度,结果为 5 echo `expr index "$string" l` # # 查找字符 "l" 在字符串中的位置,结果为 3 echo `expr substr "$string" 2 4` # #获取字符串从第二个开始,长度为4的子串,结果为 "ello"
整数表达式
算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
+ -
加减运算。两端参数会转换为整数,如果转换失败则报错。* / %
乘,除,取模运算。两端参数会转换为整数,如果转换失败则报错。
乘法要加反斜杠转义()
可以改变优先级,但需要用反斜杠转义
例:
a=3
b=4
echo `expr $a + $b` # 输出7
echo `expr $a - $b` # 输出-1
echo `expr $a \* $b` # 输出12,*需要转义
echo `expr $a / $b` # 输出0,整除
echo `expr $a % $b` # 输出3
echo `expr \( $a + 1 \) \* \( $b + 1 \)` # 输出20,值为(a + 1) * (b + 1)
shell标准输入和输出
read命令
read
命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code为1,否则为0。
参数说明
-p: 后面可以接提示信息
-t:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令
echo命令
-e
-e
是一个常用的选项(或叫做参数),它用于在使用 echo 命令输出文本时启用转义。具体来说,-e 选项会使得 echo 命令解释特定的转义字符,如 \n 表示换行,\t 表示制表符等。
\n
:换行\c
:不换行\t
:tab
如果没有使用 -e 选项,echo 命令会将\n
等 当作普通字符,而不是解释为转义符。
例:echo -e "ha\nhah" echo -e "Hello \c" echo "World" echo "dsads\tds"
输出:
ha hah Hello World dsads ds
printf命令
格式化输出 和c++中语法基本类似
格式:printf format [arguments...]
例:
printf "Hello, World!\n" # 输出普通文本,带有换行符 printf "The result is: %d\n" 42 # 输出整数,带有换行符 name="John" age=30 printf "Name: %s, Age: %d\n" "$name" "$age" # 使用格式替换标记输出变量
printf "%-10s %5d\n" "Apple" 3 # 输出字符串左对齐宽度为10,整数右对齐宽度为5
printf "%10s %-5d\n" "Apple" 3 # 输出字符串右对齐宽度为10,整数左对齐宽度为5
printf "Value: %.2f\n" 3.14159 # 输出浮点数保留2位小数
输出:
Hello, World!
The result is: 42
Name: John, Age: 30
Apple 3
Apple 3
Value: 3.14
## shell中的test命令与判断符号[]
在Bash中,test
命令和 [...]
是用于条件测试和判断的工具。它们用于检查条件的真假,并根据条件的结果来决定是否执行特定的代码块。test
命令和 [...]
可以完成相同的任务,但使用 [...]
更常见,因为它是内置的条件测试语法。
这两个命令的一般语法是:
``` bash
test condition
[ condition ]
这里注意
- []内的每一项都要用空格隔开
- 中括号内的变量,最好用双引号括起来
- 中括号内的常数,最好用单或双引号括起来
这里的 condition 是要进行判断的条件表达式。条件表达式可以包含文件测试、字符串比较、数值比较等不同类型的条件。
文件测试
命令格式:
test -letter filename
或者[ -letter filename ]
-e
文件是否存在-f
是否为文件-d
是否为目录-e
文件是否存在-f
是否为文件-d
是否为目录
数值比较
命令格式
test $变量 -letter $变量
或者[ "$变量" -letter "$变量" ]
-eq
a是否等于b (equal)-ne
a是否不等于b (not equal)-gt
a是否大于b(greatthan)-lt
a是否小于b(lessthan)-ge
a是否大于等于b(great+equal)-le
a是否小于等于b(less+equal)
字符串比较
-z STRING
判断STRING是否为空,如果为空,则返回- true-n STRING
判断STRING是否非空,如果非空,则返回- true(-n可以省略)str1 == str2
判断str1是否等于str2str1 != str2
判断str1是否不等于str2
多重条件判定
格式
test -r filename -letter -x filename
或者[ -r "filename" -letter -x "filename" ]
其中:letter:
-a
两条件是否同时成立-o
两条件是否至少一个成立!
取反。如 test ! -x file,当file不可执行时,返回true
shell判断语句
格式
if [ condition1 ]
then
echo 11
elif [condition2]
then
echo 22
else
echo 其他
fi
例:
a=4
if [ "$a" -eq 1 ]
then
echo ${a}等于1
elif [ "$a" -eq 2 ]
then
echo ${a}等于2
elif [ "$a" -eq 3 ]
then
echo ${a}等于3
else
echo 其他
fi
结果输出其他
shell循环语句
for variable in list do....done格式
格式如下:
for variable in list
do
# 执行循环中的代码块
done
这里的list可以换成 一些命令的stdout,例如ls、seq(seq 是一个用于生成序列的命令)以及大括号生成序列({1..10})等等,
例:
for file in `ls`
do
echo $file
done
#输出当前目录下的所有文件名
for i in $(seq 1 10)
do
echo $i
done
#输出1-10
for i in {a..z}
do
echo $i
done
#输出a-z
for ((…;…;…)) do…done格式
bash中也支持像c++中那样的for循环格式,只不过要两层括号
for ((i=1; i<=10; i++))
do
echo $i
done
while…do…done格式
例:
while read name
do
echo $name
done
类比于 while(scanf("%d",&x)!=EOF)或者while(cin>>x)
until…do…done循环
当until
后边的条件为真时退出循环,和while是相反的
until condition
do
语句1
语句2
...
done
break和continue
和c++中没什么区别
shell中的函数
- 一般语法:
function_name() { # 函数体(命令或代码块) }
或者
function function_name { # 函数体(命令或代码块) }
- 函数的输入参数
在函数内,$1表示第一个输入参数,$2表示第二个输入参数,依此类推。
注意:函数内的$0仍然是文件名,而不是函数名。 - 函数内的局部变量
可以在函数内定义局部变量,作用范围仅在当前函数内。
一般可以在递归函数中定义局部变量:local 变量名=变量值
注意事项:
但是值得注意的是,在其他我学过的语言中(目前就接触了js、c++、python)获得获取函数返回值都是以这个形式:x=function_name()
并且这个返回值是任意的,就是可以返回指针、整形、字符型等等,但是bash中的返回值是返回的exit code
,取值为0-255,0表示正常结束,返回其他的会报错,如果想像其他语言那样的用法的话,一般是将想要返回的值通过echo输出到stdout,然后通过$(function_name)
获取stdout的值,同时,如果实在是想获得返回的exit code,也不能直接通过=赋值,需要通过$?
来获取
例:
bash中的递归:计算阶乘
factorial() {
if [ "$1" -eq 0 ]; then
echo 1
else
local n="$1"
((n--))
local sub_result=$(factorial "$n")
echo $((sub_result * $1))
fi
}
# 调用递归函数
echo $(factorial 5)
#输出:120
对比一下c++的阶乘:
int factorial(int n) {
if (n == 0 || n == 1) {
return 1; // 递归终止条件,0和1的阶乘均为1
} else {
return n * factorial(n - 1); // 递归调用计算子问题的阶乘
}
}
可以看到在bash中,是不能直接return的,需要我们先echo想要return的值,然后通过 $(factorial "$n") 来获取这个值
还有就是为什么只输出120不输出其他的值,明明每层都有echo,
这是因为只要在echo后边有一个$,这个$就会截掉stdout中的值,所以echo找不到stdout中的值而无法输出,
在递归往回调用的过程中上一层的echo会被这一层的local sub_result=$(factorial "$n")中的$截断而无法输出,而在最后一层的echo会被调用这个函数时的echo $(factorial 5)中的$截断,无法输出,同理,如果将echo去掉直接写factorial 5,也是可以输出120的,就是输出的最后一层的120而不是调用时的120.
shell中的exit命令
1.在Bash中,exit 是一个用于终止脚本执行的内置命令。它用于退出当前的Bash脚本或Shell会话,并返回一个介于0-255退出状态码,通常用于表示脚本的执行结果或状态,只有0表示成功。默认为0,这个状态码可以通过$?
来获取
2.在子shell中使用 exit 命令将只会退出当前子shell,不会影响父shell。但在主脚本或交互式Shell中使用 exit 命令将终止整个脚本或Shell会话。
exit 与return 的区别
return和exit的共同之处都是返回exit code,区别是return结束当前函数,exit结束整个shell脚本
shell文件重定向
每个进程默认打开3个文件描述符:
- stdin标准输入,从命令行读取数据,文件描述符为0
- stdout标准输出,向命令行输出数据,文件描述符为1
- stderr标准错误输出,向命令行输出数据,文件描述符为2
可以用文件重定向将这三个文件重定向到其他文件中。
命令 | 说明 |
---|---|
command > file | 将 stdout 重定向到 file 中 |
command < file | 将 stdin 重定向到 file 中 |
command >> file | 将 stdout 以追加方式重定向到 file 中 |
command n> file | 将文件描述符 n 重定向到 file 中 |
command n>> file | 将文件描述符 n 以追加方式重定向到 file 中 |
例:
echo -e "Hello \c" > output.txt # 将stdout重定向到output.txt中
echo "World" >> output.txt # 将字符串追加到output.txt中
read str < output.txt # 从output.txt中读取字符串
echo $str # 输出结果:Hello World
shell引入外部脚本
类似 include、import等操作
语法格式:
. filename # 注意点和文件名之间有一个空格
#或
source filename
source就相当于将filename执行了一遍,所以路径一定要正确,可以使用绝对路径