本文旨在对y总的Linux基础课shell语法做学习记录,指令较多,方便日后查找。
参考视频:Linux基础课
参考教程:Linux教程
概论
Linux中常见的shell脚本有很多种,常见的有:
Linux系统中一般默认使用bash,文件开头需要写#! /bin/bash
,指明bash为脚本解释器
示例:
mkdir -p Test
vim test.sh
i:进入编辑模式
#! /bin/bash
echo "hello"
Esc:退出编辑模式
:wq 保存并退出
chmod +x test.sh: 使脚本具有可执行权限
./test.sh: 当前路径下执行
/home/XXX/test.sh: 绝对路径下执行
~/test.sh: 家目录路径下执行
bash test.sh
注释
每行中#
之后的内容均是注释
#! /bin/bash
echo 'Hello World' # 这是注释
格式:
:<<EOF
第一行注释
第二行注释
第三行注释
EOF
EOF
可以换成其它任意字符串(abc,!)
变量
a='big' # 单引号定义字符串
b="big" # 双引号定义字符串
c=big # 也可以不加引号,同样表示字符串
$
,或者${}
,花括号是可选的,主要为了帮助解释器识别变量边界
name=bigdavid
echo $name
echo ${name}
echo ${name}big # 输出bigdavidbig
使用readonly
或者declare
可以将变量变为只读
name=bigdavid
readonly name
declare -r name
name=abc ##报错,因为变量只读
unset
可以删除变量
name=bigdavid
unset name
echo $name # 输出空行
自定义变量->环境变量:
name=bigdavid
export name # 方法一
declare -x name # 方法二
环境变量->自定义变量:
export name=bigdavid # 定义环境变量
declare +x name # 改为自定义变量
字符串可以用单引号,也可以用双引号,也可以不用引号
单引号与双引号的区别:
- 单引号中的内容会原样输出,不会执行、不会取变量;
- 双引号中的内容可以执行、可以取变量;
name=bigdavid
echo 'sd,$name' # 输出 sd,$name
echo "sd,$name" # 输出 sd,bigdavid
name=bigdavid
echo ${#name} # 输出8
name=bigdavid
echo ${name:0:3} # 提取从0开始的3个字符 输出big
Tip:
(1)定义变量时,等号两边不能有空格
(2)定义变量的时候变量都是字符串,但当变量需要是整数时,会自动把变量转换成整数
(3)被声明为只读的变量无法被unset删除
(4)字符串中,不加引号和双引号效果相同
(5)如果一个变量不存在的话,他的值是空字符串
(6)默认变量是局部变量(自定义变量)
(7)左闭右开取子串
默认变量
文件参数变量
在执行shell脚本时,可以向脚本传递参数。
$1
是第一个参数,$2
是第二个参数,以此类推。特殊的,$0
是文件名(包含路径)
示例:
创建文件vim test.sh
#! /bin/bash
echo "文件名:"$0
echo "第一个参数:"$1
echo "第二个参数:"$2
echo "第三个参数:"$3
echo "第四个参数:"$4
执行脚本
chmod +x test.sh
./test.sh 1 2 3 4
输出:
文件名:./test.sh
第一个参数:1
第二个参数:2
第三个参数:3
第四个参数:4
Tip:向脚本传递参数时,参数个数超过一位需要用大括号括起来 echo ${10}
$#
:代表文件传入的参数个数
$*
:由所有参数构成的用空格隔开的字符串 "$1 $2 $3 $4"
$@
:每个参数分别用双引号括起来的字符串,如上例中值为"$1" "$2" "$3" "$4"
$$
:脚本当前运行的进程ID
$?
:上一条命令的退出状态,0表示正常退出,其他值表示错误。(不是stdout,而是exit code)
$(command)
:返回command这条命令的stdout(可嵌套)
(可嵌套) 和 (不可嵌套) 区别是:可嵌套就是可以实现多个命令
#! /bin/bash
name=big
echo $(echo $name
name=sd
echo $name)
输出:
bigdavid
asd
数组
数组中可以存放多个不同类型的值,只支持一维数组,初始化时不需要指明数组大小。
数组下标从0开始。
数组用小括号表示,元素之间用空格隔开
array=(1 abc "def")
array[0]=1
array[1]=abc
array[2]="def"
echo ${array[index]}
${array[@]}
${array[*]}
${#array[@]}
${#array[*]}
Tip:
在读取整个数组时,未定义的部分就不会显示。比如定义了array[0]array[1]array[2]array[1000],利用echo ${array[@]}输出时,只会显示我们定义的数组那4个元素的值。长度时,结果是4,而不是1000
expr命令
expr 表达式
命令用于求表达式的值
- 用空格隔开每一项
- 用反斜杠放在shell特定的字符前面(发现表达式运行错误时,可以试试转义)
- 对包含空格和其他特殊字符的字符串要用引号括起来
- expr会在stdout中输出结果。如果为逻辑关系表达式,则结果为真时,stdout输出1,否则输出0
- expr的exit code:如果为逻辑关系表达式,则结果为真时,exit code为0,否则为1
(1)length XXX
:返回XXX的长度
(2)index XXX YYY
:YYY中任意单个字符在XXX中最前面的字符位置,下标从1开始。如果XXX中完全不存在YYY中的字符,则返回0。
(3)substr XXX A B
:返回XXX字符串中从A开始,长度最大为B的子串。如果A或B为负数,0或非数值,则返回空字符串
示例:
str=Hello
echo `expr length $str` #输出5
echo `expr index $str l`#输出3
echo `expr substr $str 2 3`#输出ell
expr支持普通的算术操作
优先级:字符串表达式 > 算术表达式 > 逻辑关系表达式
+ -
:加减运算。两端参数会转换为整数,如果转换失败则报错。* / %
:乘,除,取模运算。两端参数会转换为整数,如果转换失败则报错。
()
:可以改变优先级,但需要用反斜杠转义
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)
|
&
:如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参为0或为空,则不会计算第二个参数。< <= = == != >= >
:比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。()
:改变优先级,但需要用反斜杠转义Tip:
(1):乘法运算的 * 的使用除转义外,还有直接加单引号这种方式也可以
echo `expr $a '*' $b`
(2):单引号表示原生字符串,单引号里面的是啥就是啥,双引号会把字符串格式化
str='Hello World!'
echo expr length '$str' # 输出为4,原始字符串$str
echo expr length "$str" # 输出为12,变量值为Hello World!
read命令
read
命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code
为1,否则为0。
参数:
-p
: 后面可以接提示信息-t
:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令read name# 读入name的值
bigdavid #标准输入
echo $name # 输出name的值
bigdavid #标准输出
read -p "Please input your name: " -t 3 name # 读入name的值,等待时间3秒
Please input your name: bigdavid # 标准输入
echo $name # 输出name的值
bigdavid #标准输出
Tip:
-t
和 -p
的顺序不影响实际效果
echo命令
echo
用于输出字符串。
echo "sad"
echo asd
echo "\"df\""
echo \"sd\"
name=bigdavid
echo "sd is $name"
echo -e "hi\n" # -e 开启转义
echo -e "Hi \c" # -e 开启转义 \c 不换行
将内容以覆盖的方式输出到output.txt中
echo "Hello World" > output.txt
name=bigdavid
echo '$name\"'
echo `date`
echo $(date)
Tip:
(1)查看echo相关转移字符,在终端输入help echo
(2)输出内容定向文件时,哪里的文件都可以定向,如果不是同一文件下,那要把路径写清楚,如果定向的文件不存在,会默认给你新建一个文件
printf命令
printf
命令用于格式化输出,类似于C/C++中的printf函数。
默认不会在字符串末尾添加换行符
格式 :printf format-string [arguments...]
示例:
printf "%10d.\n" 123 # 占十位,右对齐
printf "%-10.2f.\n" 123.1234 # 占10位,保留2位小数,左对齐
printf "%s" "bigdavid" # 格式化输出字符串
printf "%d * %d = %d\n" 2 3 `expr 2 /* 3`
test命令与判断符号[]
&&
表示与,||
表示或expr1 && expr2
:当expr1
为假时,直接忽略expr2
expr1 || expr2
:当expr1
为真时,直接忽略expr2
exit code
为0,表示真;为非零,表示假。(与C/C++中的定义相反)终端输入:help test
或man test
,查看test命令的用法。
(1)test命令用于判断文件类型,以及对变量做比较。
(2)test命令用exit code返回结果,而不是使用stdout。0表示真,非0表示假。
判断文件是否存在
格式:test -e filename
测试参数 | 代表意义 |
---|---|
-e | 文件是否存在 |
-f | 是否为文件 |
-d | 是否为目录 |
判断文件是否可读
格式:test -r filename
测试参数 | 代表意义 |
---|---|
-r | 文件是否可读 |
-w | 文件是否可写 |
-x | 文件是否可执行 |
-s | 是否为非空文件 |
a是否等于b
格式:test $a -eq $b
测试参数 | 代表意义 |
---|---|
-eq | a是否等于b |
-ne | a是否不等于b |
-gt | a是否大于b |
-lt | a是否小于b |
-ge | a是否大于等于b |
-le | a是否小于等于b |
测试参数 | 代表意义 |
---|---|
test -z STRING | 判断STRING是否为空,如果为空,则返回true |
test -n STRING | 判断STRING是否非空,如果非空,则返回true(-n可以省略) |
test str1 == str2 | 判断str1是否等于str2 |
test str1 != str2 | 判断str1是否不等于str2 |
格式:test -r filename -a -x filename
测试参数 | 代表意义 |
---|---|
-a | 两条件是否同时成立 |
-o | 两条件是否至少一个成立 |
! | 取反。如 test ! -x file,当file不可执行时,返回true |
[]与test用法几乎一模一样,更常用于if语句中。另外[[]]是[]的加强版,支持的特性更多
当使用
[]
出现[: -eq: unary operator expected
的错误时,可以使用[[]]
。
[ 2 -lt 3 ] # 为真,返回值为0
echo $? # 输出上个命令的返回值,输出0
Tip:
[]
内的每一项都要用空格隔开- 中括号内的变量,最好用双引号括起来
- 中括号内的常数,最好用单或双引号括起来
-eq :equal(相等)
-ne :not equal(不等)
-gt :greater than(大于)
-ge :greater than or equal(大于或等于)
-lt :less than(小于)
-le :less than or equal(小于或等于)
if…then形式(类似于C/C++中的if-else语句)
if condition
then
语句1
语句2
...
fi
if condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
if condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
类似与C/C++的switch语句
示例:
a=4
case $a in
1)
echo ${a}
;; # 类似于C/C++中的break
2)
echo ${a}
;;
3)
echo ${a}
;;
*) # 类似于C/C++中的default
echo 其他
;;
esac
for...in...do...done
格式:
for var in val1 val2 val3
do
语句1
语句2
...
done
实例:
(1)输出a 2 cc,每个元素一行
for i in a 2 cc
do
echo $i
done
(2)输出当前路径下的所有文件名,每个文件名一行
for file in `ls`
do
echo $file
done
(3)输出1-10
for i in $(seq 1 10)
do
echo $i
done
(4)使用{1..10}
或者 {a..z}
for i in {a..z}
do
echo $i
done
for ((…;…;…)) do…done
格式:
for ((expression; condition; expression))
do
语句1
语句2
done
for ((i=1; i<=10; i++))
do
echo $i
done
while…do…done循环
格式:
while condition
do
语句1
语句2
...
done
文件结束符为Ctrl+d
,输入文件结束符后read指令返回false
while read name
do
echo $name
done
until…do…done循环
当条件为真时结束
格式:
until condition
do
语句1
语句2
...
done
当用户输入yes
或者YES
时结束,否则一直等待读入。
until [ "${word}" == "yes" ] || [ "${word}" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " word
done
break命令
跳出当前一层循环,注意与C/C++不同的是:break不能跳出case语句。
实例:
while read name
do
for ((i=1; i<=10; i++))
do
case $i in
8)
break
;;
*)
echo $i
;;
esac
done
done
该示例每读入非EOF的字符串,会输出一遍1-7。
该程序可以输入Ctrl+d
文件结束符来结束,也可以直接用Ctrl+c
杀掉该进程
continue命令
跳出当前循环
实例:
for ((i=1; i<=10; i++))
do
if [ `expr $i % 2` -eq 0]
then
continue
fi
echo $i
done
该程序输出1-10中的所有奇数
遇到死循环,输入Ctrl+c
关闭进程:
top
命令找到进程的PIDkill -9 PID
即可关掉此进程ps aux
返回当前打开的所有进程bash中的函数类似于C/C++中的函数,但return的返回值与C/C++不同,返回的是exit code
,取值为0-255,0表示正常结束,注意区别。
函数的return值可以通过echo $?
来获取
如果想获取函数的输出结果,可以通过
echo
输出到stdout
中,然后通过$(function_name)
来获取stdout
中的结果。
格式:
[function] func_name() {
语句1
语句2
...
}
return
值和stdout
值func() {
name=bigdavid
echo "$name"
}
func
return
值和stdout
值不写return
时,默认return 0
func() {
name=bigdavid
echo "$name"
return 123
}
output=$(func)
ret=$?
echo "output = $output"
echo "return = $ret"
在函数内,$1
表示第一个输入参数,$2
表示第二个输入参数,依此类推。
注意:函数内的
$0
仍然是文件名,而不是函数名
func() { # 递归计算 $1 + ($1 - 1) + ($1 - 2) + ... + 0
word=""
while [ "${word}" != 'y' && "${word}" != 'n' ]
do
read -p "要进入func($1)函数嘛?请输入y/n: " word
done
if [ "$word" == 'n' ]
then
echo 0
return 0
fi
if [ $1 -le 0 ]
then
echo 0
return 0
fi
sum = $(func $(expr $1 - 1))
echo $(expr $sum + $1)
}
echo $(func 10)
可以在函数内定义局部变量,作用范围仅在当前函数内。
可以在递归函数中定义局部变量。
格式:local 变量名=变量值
#! /bin/bash
func() {
local name=bigdavid
echo $name
}
func
echo $name
输出结果
bigdavid
第一行为函数内的name变量,第二行为函数外调用name变量,会发现此时该变量不存在
shell语法中函数的变量若无声明 就默认是全局变量
exit
命令用来退出当前shell
进程,并返回一个退出状态;使用$?
可以接收这个退出状态。
exit
命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit
退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
创建脚本test.sh:vim test.sh
#! /bin/bash
if [ $# -ne 1 ]
then
echo "not valid"
exit 1
else
echo "valid"
exit 0
fi
chmod +x test.sh
./test.sh bigdavid #正常退出
echo $? # 0
./test.sh #不正常退出
echo $? # 1
return和exit的共同之处都是返回exit code,区别是return结束当前函数,exit结束整个shell脚本
每个进程默认打开3个文件描述符:
stdin
标准输入,从命令行读取数据,文件描述符为0stdout
标准输出,向命令行输出数据,文件描述符为1stderr
标准错误输出,向命令行输出数据,文件描述符为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
#! /bin/bash
read a
read b
echo $(expr "$a" + "$b")
创建input.txt:vim input.txt
内容:
3
4
chmod +x test.sh
# 从input.txt中读取内容,将输出写入output.txt中
./test.sh < input.txt > output.txt
cat output.txt # 查看output.txt中的内容
- ls -l >文件 (列表的内容写入文件a.txt中 覆盖写)
- ls -al >>文件 (列表的内容文件追加到文件aa.txt的末尾)
- cat 文件1 >文件2 (将文件1的内容覆盖到文件2)
- echo “内容” >>文件 (将echo的内容追加到文件末尾)
类似于C/C++中的include,bash也可以引入其他文件的代码
格式:
. filename # 注意点和文件名之间有一个空格
或
source filename
示例:
vim test1.sh
#! /bin/bash
name=bigdavid
vim test2.sh
#! /bin/bash
source test1.sh # . test1.sh
echo $name
输出:
bigdavid
引入的文件可以添加路径,比如使用绝对路径source /home/XXX/test1.sh
本文介绍了shell语法的常见用法,不足之处望指正。