SHELL编程
Shell 是一种用户与操作系统内核进行交互的界面。它是命令行解释器,用户通过输入命令,Shell 解释并执行这些命令,从而操作系统中的各种功能得以实现。
- 脚本需要以
#!/bin/bash
开头,并赋予执行权限 - 脚本的执行方式:
- 赋予脚本执行权限
chmod u+x hello.sh
,然后通过相对路径./hello.sh
或通过绝对路径/home/shcode/hello.sh
- 不赋予脚本执行权限,直接执行
sh hello.sh
- 赋予脚本执行权限
建议都直接加权限然后通过./运行
Q: 为什么有些命令通过sh hello.sh
无法运行,然而通过chmod u+x hello.sh
之后再./hello.sh
就能运行起来呢?例如$[1+2]$
。
A: 在Unix/Linux系统中,当你使用 sh hello.sh
来运行脚本时:
- 使用的是
/bin/sh
解释器:sh
通常指向/bin/sh
,这是一个兼容POSIX标准的Shell。在大多数现代Linux系统中,/bin/sh
是指向bash
的符号链接,但它以POSIX兼容模式运行,禁用了bash
的一些特性。 - 忽略脚本中的shebang行:
sh
命令会忽略脚本中的shebang行(#!/bin/bash
),而是直接使用/bin/sh
解释器。
当你通过 chmod u+x hello.sh
使脚本可执行,并使用 ./hello.sh
运行脚本时:
- 使用shebang行指定的解释器:如果脚本的第一行是
#!/bin/bash
,它会使用bash
解释器运行脚本。因此,脚本中的命令会按照bash
的特性解释和执行。 - 脚本本身成为一个可执行文件:通过
chmod u+x
,你使得脚本本身变成一个可执行文件,你可以直接通过路径来执行它。
假设 hello.sh
包含以下内容:
#!/bin/bash
echo $[1+2]
-
使用
sh hello.sh
:- 由于
sh
以POSIX模式运行,$[1+2]
这种特性可能会被忽略或引发错误。
- 由于
-
使用
chmod u+x hello.sh
之后./hello.sh
:- 这时脚本会使用
#!/bin/bash
指定的bash
解释器来运行,因此echo $[1+2]
会正常输出3
。
- 这时脚本会使用
为了确保您的脚本在不同的运行方式下都能正常工作,可以使用更加通用的语法。例如,使用 $((1+2))
进行算术运算,这是POSIX兼容的:
#!/bin/bash
echo $((1+2))
这样,无论是通过 sh hello.sh
还是 ./hello.sh
运行脚本,都会得到相同的结果。
sh hello.sh
使用/bin/sh
解释器并忽略shebang行,可能不支持某些bash
特性。./hello.sh
使用shebang行指定的解释器(如bash
),支持更多的特性。- 确保脚本兼容POSIX标准,可以提高其在不同环境下的可移植性。
#!/bin/bash
echo "hello,world!"
单行注释:#注释内容
多行注释:
:<<!
注释内容
!
Shell变量
Shell变量分为系统变量和用户变量,系统变量有$HOME
, $PWD
, $SHELL
, $USER
等。
Shell中定义变量:变量名=值
,在Shell中不要乱加空格,在使用变量时需要在前面加上$
。
撤销变量:unset 变量
。声明静态变量:readonly 变量
,静态变量不能被撤销
将命令的返回值赋值给变量:A=`date`,用反引号表示里面是一条命令,如果不加反引号会默认是一个字符串,或者A=$(date)
也可以
#!/bin/bash
#定义变量A
A=100
#输出变量
#从结果可以看出无论是否加双引号都可以
echo 1A=$A
echo "2A=$A"
#撤销变量A
#撤销后就相当于这个变量不存在了
unset A
echo "3A=$A"
#定义静态变量B,不能撤销
readonly B=2
echo "1B=$B"
#不能撤销会报错
#unset B
#echo "2B=$B"
C=`date`
D=$(date)
echo "C=$C"
echo "D=$D"
#多行注释不会输出
:<<!
echo "注释内容"
!
1A=100
2A=100
3A=
1B=2
C=Thu May 16 03:29:45 PM CST 2024
D=Thu May 16 03:29:45 PM CST 2024
配置环境变量:
export 变量名=变量值
source 配置文件
echo $变量名
#修改/etc/profile文件中的环境变量
export TOMCAT_HOME=/opt/tomcat
#生效配置文件
source /etc/profile
#输出变量
echo $TOMCAT_HOME
位置参数变量:执行shell脚本的时候会希望能获取到命令行的参数信息,如./hello.sh 100 200
$n
:$0
代表命令本身,$1
代表接收到的第一个参数,如果是10以上的参数需要加括号${10}
$*
:(变成一整个字符串来存储)
- 在参数引用中,将所有参数作为一个单独的字符串引用。所有参数被合并成一个字符串,每个参数之间用第一个字符的值分隔(默认是空格)。
- 在双引号中,将所有参数合并成一个字符串,每个参数之间用IFS(内部字段分隔符)分隔。默认情况下,IFS是空格。
$@
:(变成一个列表来存储)
- 在参数引用中,将所有参数作为独立的字符串引用。每个参数保持独立,并且在被引用时,不会合并成一个单独的字符串。
- 在双引号中,将每个参数保持独立。即使在双引号内,每个参数仍然是独立的字符串。
$*加双引号其实是变成了一个插值字符串
$#
:接收到的命令行中所有参数的个数
#!/bin/bash
echo "Using \$*:"
for arg in $*; do
echo "$arg"
done
echo "Using \$@:"
for arg in $@; do
echo "$arg"
done
echo "Using \"\$*\":"
for arg in "$*"; do
echo "$arg"
done
echo "Using \"\$@\":"
for arg in "$@"; do
echo "$arg"
done
echo "NUM=$#"
Using $*:
10
20
30
40
Using $@:
10
20
30
40
Using "$*":
10 20 30 40
Using "$@":
10
20
30
40
NUM=4
预定义变量
$$
:返回当前进程的PID
$!
:返回后台运行的最后一个进程的进程号
$?
:返回最后一个执行命令的返回状态。若为0表示上一条命令正确执行,否则表示上一条命令执行错误。
在后台运行脚本的方式:/root/home/shcode/myshell.sh &
,在命令后加&
运算符
基本语法:$((运算式))
,$[运算式]
,expr m + n
建议使用$[]$
即可
其中expr运算符之间要有空格,如果希望将expr的结果赋值给变量,需要加反引号``
在expr中乘法是\*
,除法和取余是正常的/,%
,在前一个运算式中乘法是正常的*
#!/bin/bash
#(2+3)*4
RES1=$(((2+3)*4))
#expr的结果赋值需要加反引号
#expr的运算符之间需要有空格
TEMP=`expr 2 + 3`
RES2=`expr $TEMP \* 4`
echo "RES1=$RES1"
echo "RES2=$RES2"
#接受命令行输入并求和
SUM=$(($1+$2))
echo "SUM=$SUM"
条件判断
判断条件(这种比较一般只用于[]
的判断中):
-
字符串比较:
=
-
整数比较:
-lt
小于,-le
小于等于,-eq
等于,-gt
大于,-ge
大于等于,-ne
不等于 -
按照文件权限进行判断:
-r
读权限,-w
写权限,-x
执行权限 -
按照文件类型进行判断:
-f
文件存在并且是一个常规文件,-e
文件存在,-d
文件存在并是一个目录
基本格式:
[ condition ]
前后必须要有空格,非空返回true(0为true),否则返回false。
if [ condition ]
then
语句
elif [ condition ]
then
语句
fi
[ condition ] && A || B
:若条件成立执行A,不成立则执行B。
#!/bin/bash
#"ok"是否等于"ok"
if [ "OK"="OK" ]
then
echo "equal"
fi
#23是否大于等于22
if [ 23 -ge 22 ]
then
echo "greater or equal"
fi
#/root/shcode/aaa.txt 目录中的文件是否存在
if [ -f /root/shcode/aaa.txt ]
then
echo "exist"
fi
#只要不空即为true
if [ hspedu ]
then
echo "hspedu"
fi
#条件表达式
[ "OK"="OK" ] && echo "equal" || echo "no equal"
#多重判断
if [ $1 -ge 60 ]
then
echo "pass"
elif [ $1 -lt 60 ]
then
echo "fail"
fi
equal
greater or equal
hspedu
equal
fail
分支判断
基本格式:
case $变量名 in
"值1")
语句1
;;
"值2")
语句2
;;
*)
语句
;;
esac
#case
case $2 in
"1")
echo "语句1"
;;
"2")
echo "语句2"
;;
*)
echo "其他语句"
;;
esac
循环结构
for基本语法
for 变量 in 值1, 值2, ...
do
代码
done
for ((初始值;循环控制条件;变量变化))
do
代码
done
这里会出现一个问题:使用第二种C风格的for循环时,不能使用sh来运行,必须通过chmod加权限在./执行
#!/bin/bash
#打印命令行输入的参数
for i in "$@"
do
echo "i=$i"
done
#计算1到第一个输入的数的和
SUM=0
for(( i=1; i<=$1; i++ ))
do
SUM=$(($SUM + $i))
done
echo "SUM=$SUM"
while基本语法
while [ 条件判断 ]
do
代码
done
#!/bin/bash
#接受从键盘的输入,并统计从1加到n的和
SUM=0
i=0
while [ $i -le $1 ]
do
SUM=$(($SUM+$i))
i=$(($i+1))
done
echo "SUM=$SUM"
读取输入
read (选项) (参数)
选项:-p
:读取指令时的提示符 -t
:指定读取等待时间(秒)
参数:指定读取值的变量名
#!/bin/bash
read -p "请输入一个数NUM=" NUM
echo "NUM=$NUM"
函数
分为系统函数和自定义函数。
basename [pathname]
返回完整路径的最后的文件名,dirname [pathname]
返回完整路径前面的路径
basename /root/shcode/aaa.txt
输出:aaa.txt
dirname /root/shcode/aaa.txt
输出:/root/shcode
自定义函数:
function myFunction {
代码
}
定义函数后,可以直接通过函数名来调用。函数中的参数通过$1
,$2
来访问
Shell函数不能直接返回值,但可以通过 echo
或设置全局变量来返回结果。
return
关键字在Shell中用于返回一个状态码(退出码),通常表示成功或失败。
#!/bin/bash
# 定义函数
function add {
#内部通过$1$2来使用值
local result=$(( $1 + $2 ))
echo $result
}
function subtract {
local result=$(( $1 - $2 ))
echo $result
}
# 使用 read 命令获取用户输入
read -p "Enter first number: " n1
read -p "Enter second number: " n2
# 调用函数并获取结果
sum=$(add $n1 $n2)
diff=$(subtract $n1 $n2)
# 输出结果
echo "Sum: $sum"
echo "Difference: $diff"