视频教程
- “$#” 表示运行脚本或者调用函数时,输入的参数个数,不包括脚本文件名以及函数名;
- “$0” 不管在函数外部还是函数内部,都表示当前脚本文件名;
- “$1” 在函数内部,表示传递给函数的第一个参数;在函数外部表示传递给脚本的第一个参数;“$2” “$3” 意义也一样;
- “$$” 表示当前脚本的 pid;可以通过 “kill -9 pid” 指令退出指定进程;
- 调试 “bash -x scriptName”,通过这个调试命令,可以查看整个脚本的运行过程;
cat /etc/shells # 查看当前系统支持的shell
echo $SHELL # 查看当前系统默认的shell
echo -e "Enter the info: \c" # -e使能转义字符,\c不换行
echo $(ls) # 执行Linux命令
(( n++ )) # n自增1
n=$(( n+1 )) # n自增1
# 以下这句命令利用逻辑运算符的短路运算,可以判断文件是否存在
# [ -f fileName ] : 如果 fileName 存在,其返回值为 0,但是这个 0 是被当作 true 理解的。(返回值可以通过 echo $? 指令查看)
# 当 [ -f fileName ] 为 true 时,因为后面是 &&,就会继续判断 && 后面的条件(这里是执行 echo 语句),这里必须要保证 && 后面的语句执行状态为 true,否则将会继续执行 || 后面的语句;
# 当 [ -f fileName ] 为 false 时,将会直接跳过 && 后面的语句,直接执行 || 后面的语句;
[ -f fileName ] && echo "file exsit" || echo "file not found"
#! /bin/bash
echo current work dir is $PWD
echo $SHELL
echo $BASH
echo $BASH_VERSION
pi=3.14 # 系统变量一般大写,自定义变量小写,且不能以数字开头
echo $pi
echo enter your name:
read name
echo your name is $name
echo enter three string:
read str1 str2 str3 # 一次获取多个输入值,以空格间隔(中间有空格需加引号),回车结束输入
echo your input $str1 $str2 $str3
read -p "enter your name: " name # -p 可以添加输入提示
echo your name is $name
read -sp "enter your password: " password # 静默输入(输入时不显示); -sp 不能写成 -ps;
echo your possword is $password
read -p "enter names: " -a names # -a 表示输入一个数组,空格间隔,回车结束输入
echo ${names[0]} ${names[1]} # 输出数组中项的值
echo $REPLY
read # 当read后没有带变量名时,默认将输入存储在系统变量 $REPLY 中
echo $REPLY
echo totally $# params # $#表示参数个数(不包括文件名)
echo $0 $1 $2 # $0表示文件名;$1表示第一个参数;$2表示第二个参数
args=("$@") # $@表示所有参数; "args=$@"好像也可以
echo $@ # 输出所有参数(不包含文件名)
echo ${args[0]} ${args[1]} # args[0]表示第一个参数,而不是文件名!
符号 | 意义 | 举例 |
---|---|---|
-eq | equal | if [ “$a” -eq “$b” ] |
-ne | not equal | if [ “$a” -ne “$b” ] |
-gt | greater than | if [ “$a” -gt “$b” ] |
-ge | greater than or equal | if [ “$a” -ge “$b” ] |
-lt | less than | if [ “$a” -lt “$b” ] |
-le | less than or equal | if [ “$a” -le “$b” ] |
< | less than | ((“$a” < “$b”)) |
<= | less than or equal | ((“$a” <= “$b”)) |
> | greater than | ((“$a” > “$b”)) |
>= | greater than or equal | ((“$a” >= “$b”)) |
符号 | 意义 | 举例 |
---|---|---|
= | equal | if [ “$a” = “$b” ] |
== | equal | if [ “$a” == “$b” ] |
!= | NOT equal | if [ “$a” != “$b” ] |
< | less than, in ASCII order | if [[ “$a” < “$b” ]] |
> | greater than, in ASCII order | if [[ “$a” > “$b” ]] |
-z | string is NULL(len=0) | if [[ -z “$a” ]] |
注意到,以上比较中,不管是数值比较还是字符串比较,只要使用了 “<”, “>” 相关的符号,就必须有双重括号,这是为了覆盖 “<”, “>” 这两个符号本身的重定向功能。
count=9 # 中间不能有空格
if [ $count -ne 10 ]
then
if (( $count > 10 )) # (()) 不能用 [[]] 代替,不然将当作字符串比较
then
echo \$count is greater than 10
else
echo \$count is less than 10
fi
else
echo \$count is 10
fi
val=0 # val="" 将会导致下面的判断为假
if [ $val ] # 这里将会判断为真,因为默认 val 为字符串
then
echo true, \$val is $val
else
echo false, \$val is $val
fi
num=0
if ( $val ) # 这里判断为真,() 内部为数值判断
then
echo true, \$num is $num
else
echo false,\$num is $num
fi
num=2
if (( $num <= 5 ))
then
echo \$num\($num\) is less than or equal to 5
elif (( $num < 10 ))
then
echo \$num\($num\) is less than 10
else
echo \$num\($num\) is greater than or equal to 10
fi
符号 | 意义 | 举例 |
---|---|---|
-e | 文件是否存在 | if [ -e $file_name ] |
-f | 文件是否存在以及是否常规文件 | if [ -f $file_name ] |
-d | 文件夹是否存在 | if [ -d $dir_name] |
-c | 字符文件(文本文件) | if [ -c $file_name ] |
-b | block文件(二进制,图片,视频文件) | if [ -b $file_name ] |
-s | 文件是否为空 | if [ -s $file_name ] |
-r, -w, -x | 文件或文件夹是否有相应权限 | if [ -x $file_name ] |
echo -e "Enter the file name: \c" # -e使能转义功能,\c不换行
read name
if [ -f $name ] # 文件是否存在
then
if [ -w $name ] # 文件是否有写入权限
then
echo "append some text to file, Ctrl-D to quit: "
cat >> $name # 追加输入的内容到指定文件
else
echo "$name has no write permissions"
fi
else
echo "$name is not exist"
fi
age=25
if [ $age -gt 18 ] && [ $age -lt 30 ] # 这个写法和一些两种都一样
# if [ $age -gt 18 -a $age -lt 30 ]
# if [[ $age -gt 18 && $age -lt 30 ]]
then
echo "valid age"
else
echo "invalid age"
fi
所有的算术操作都需要在双括号 (()) 里面完成! 或者使用 expr 命令行计算器工具
但是以上两个方式都只能针对整数进行运算
echo $((1+2)) # 3
echo $(expr 1 + 2) # 3, 加号两边的空格一定要有
echo $(( 2 * 3 )) # 6
echo $(expr 2 \* 3) # 6, *前面需要转义
bc: basic calculator
echo 12.5+1.2 | bc
echo "scale=3; 2/3" | bc # scale指定保留的小数位数,且只能在除法、求平方根等特定计算中使用中使用;一定需要双引号;
echo "scale=3;sqrt(27)" | bc -l # 平方根计算; -l是为了调用数学库用于sqrt计算
echo "3^3" | bc
case $1 in # 根据第一个参数判断
"car" )
echo car
echo second line;; # 可以执行多个语句,直到 ;; 结束
"van" )
echo van
echo second line;;
* ) # default 情况
echo nothing
echo second line;;
esac
# 如果输入 ? 和 * 将出现奇怪现象,暂时不知道怎么解释
read -p "Enter a character: " ch
case $ch in
[a-z] ) # 这个条件可以是一个范围
echo user input $ch between a to z;;
[A-Z] )
echo user input $ch between A to Z;;
[0-9] )
echo user input $ch between 0 to 9;;
? ) # ?只匹配一个字符
echo user input others character $ch;;
* ) # *匹配所有字符
echo user input not only one character;;
esac
- bash 中的数组索引可以是不连续的,可以在随意索引处添加元素,而删除元素后,这个元素之后的元素索引也并不会发生变化。
- 而数组长度和索引值无关,其真实表现了数组的真实个数;比如一个数组在索引 0,4,6处有值,其长度为 3;
- 任何一个变量都可以认为是数组,变量值存储在索引为0的位置;
os=('linux' 'windows' 'MacOS')
echo ${os[@]} # 输出所有元素
echo ${os[0]} # linux
echo ${!os[@]} # 输出所有的索引,0 1 2
echo ${#os[@]} # 输出数组长度,3
os[${#os[@]}]='Android' # 添加项
echo ${os[@]}
unset os[2] # 删除索引为2的元素
echo ${!os[@]} # 查看删除元素后索引值的变化: 0 1 3; 可以看到删除2后没有将后面的index前移
n=1
while [ $n -le 5 ]
# while (( $n <= 5 )) # 这句和上面的语句等效
do
echo \$n is $n
n=$((n+1)) # 可以用 (( n++ )) 代替
sleep 1 # delay 1s
done
# 以下脚本功能:将脚本本身打印到终端
while read p # 读一行
do
echo $p
done < $0 # 以参数0(即脚本名)为输入,重定向到 while 循环
# 功能和上面的脚本一样
cat $0 | while read p # 通过管道将脚本内容输入到 while 循环
do
echo $p
done
# 功能和上面的脚本一样
while IFS=' ' read -r line # IFS指定字分隔符为空格;-r表示在读取文件中防止反斜杠的转义解释
do
echo $line
done < $0
- until循环和while循环唯一的不同就是:while循环是在条件为真时执行,而until循环是在条件为假时执行;
- 在各种循环中,也可以使用 break 跳出循环,以及使用 continue 跳过本次循环;
n=1
until (( n > 5 ))
do
echo \$n is $n
(( n++ ))
done
# 以下将会输出:1 2 .. 5 # 都当作字符串处理了
for n in 1 2 .. 5
do
echo $n
done
# 以下将输出:1 2 3 4 5
for n in {1..5} # 需要 $BASH_VERSION 大于 3.0
do
echo $n
done
# 输出:1 3 5 7 9
for n in {1..10..2} # {starValue..endValue..step} # 需要 $BASH_VERSION 大于 4.0
do
echo $n
done
# 输出:1 2 3 4 5
for (( i=0; i<5; i+=1 )) # 操作符前后可以加空格,也可以用 i++
do
echo $i
done
for n in $(ls)
do
echo $n # 依次输出 ls 命令的输出
done
# 执行命令
for cmd in ls pwd date
do
$cmd # 依次执行以上命令
done
# 输出当前路径下所有文件夹的名称(当文件夹名称中有空格时输出还有点问题)
for item in * # *将会枚举当前路径下的所有文件名和文件夹名
do
if [ -d ${item} ]
then
echo $item
fi
done
select 循环适合做和用户进行交互的菜单,给用户提供一些选择
select name in jack mark denis eleen
do
echo "$name selected"
done
select name in jack mark denis eleen
do
case $name in # select循环和case语句结合,可以方便得处理各种情况
jack)
echo jack selected
break;; # 通过break可以跳出select循环,后面的两个 ;; 是必不可少的,不然报语法错误
mark)
echo mark selected
break;;
denis)
echo denis selected
break;;
eleen)
echo eleen selected
break;;
*)
echo input error;; # 输入错误,不跳出循环,继续读取用户输入
esac
done
- 函数必须先声明,然后调用;
- 如果函数外部变量和函数内部变量同名,在调用函数之后,会修改外部变量的值;
- 可以通过关键字 local 解决上面的问题;local 只能在函数中使用;
function Hello(){ # 通过关键字 function 定义名为 Hello 的函数
echo Hello
}
quit(){ # 省略 function 关键字定义名为 quit 的函数
exit # 执行 exit 指令
}
Hello # 调用 Hello 函数
quit # 调用 quit 函数
echo hello again # 由于上面的 quit 函数已经退出了,所以这里的 echo 语句不会执行
function print(){
echo $1 # 通过 $1 访问函数的第一个参数
}
print Hello # 调用 print 函数并传入一个参数
print World # 调用 print 函数并传入一个参数
# 变量同名的问题
function print(){
var=$1
echo $var
}
var=Hello
echo $var # 输出 Hello
print World # 输出 World
echo $var # 输出 World (var的值已经在 print 函数中被改变了)
# 通过 local 解决变量同名问题
function print(){
local var=$1 # 通过 local 定义局部变量
echo $var
}
var=Hello
echo $var # 输出 Hello
print World # 输出 World
echo $var # 输出 Hello
#!/bin/bash
usage() {
echo "you should input a file name as a params"
echo "usage: $1 fileName" # 这里的 $1 表示调用这个函数的第一个参数
exit
}
is_file_exsit() {
local file=$1
[ -f $1 ] && return 0 || return 1 # 详细解释见本文最前面,简单说就是利用了逻辑运算符的短路原则
}
[ $# -eq 0 ] && usage # 判断运行脚本时是否有输入参数,如果没有,则会调用 usage 函数告诉用户需要输入一个参数并退出脚本
if ( is_file_exsit $1 ) # 判断文件是否存在,这里的 $1 表示运行这个脚本时输入的第一个参数
then
echo file found
else
echo file NOT found
fi
- 直接运行 readonly 而不指定只读对象时,将会默认设置 bash 内建的变量都变成只读变量!和 “readonly -p” 指令一样;
- "readonly -f " 可以输出所有的只读函数;
readonly var=31 # 定义 readonly 变量,readonly 也可以作用在前面已经定义的变量上
echo $var # 输出 31
var=43 # 执行错误
echo $var # 输出 31
var=12
readonly var=34 # 执行正常
echo $var # 输出34
var=12
readonly var
var=34 # 执行错误
echo $var # 输出12
hello() { # 定义 hello 函数
echo hello1
}
hello # 调用 hello 函数
hello() { # 重定义 hello 函数
echo hello2
}
hello # 调用重定义之后的 hello 函数
readonly -f hello # 将函数声明为 readonly,需要添加 -f 参数;函数不能在定义时就声明为 readonly,必须定义后再声明;
hello() { # 再次重定义 hello 函数,但是执行错误
echo hello3
}
hello # 这里输出的还是 hello2
while (( COUNT < 5 ))
do
echo $COUNT # 第一次输出为空,之后依次输出 1 2 3 4
(( COUNT++ ))
done
COUNT=0
while (( COUNT < 5 ))
do
echo $COUNT # 依次输出 0 1 2 3 4
(( COUNT++ ))
done
- 通过 “man 7 signal” 可以查看都有哪些信号;
EXIT -------> (Unknow) ------> 0
SIGINT -------> CTRL+C ------> 2
SIGTSTP -------> CTRL+Z ------> 20
SIGKILL -------> kill -9 pid ------> 9- trap “code to be executed” sig_number # 这个 trap 语句必须写在出现这个信号之前的地方,不然无效
- <
> 和 < > 完全等价; - 触发 trap 语句后,这个信号原有的功能就失效了,只执行用户在双引号中自定义的功能;然后从触发此信号时正在执行的语句的下一条处开始执行;
- trap 不能捕获 SIGKILL信号 和 SIGSTOP信号;通过 “kill -9 pid”(等价于"kill -SIGKILL pid")可以发送SIGKILL信号,通过 “kill -SIGSTOP pid” 可以发送 SIGSTOP信号;经过SIGSTOP信号暂停的脚本,可通过SIGCONT信号使其继续执行;
- 疑问:可以通过 “kill -SIGINT pid” 代替 CTRL-C 吗?
- 好像可以通过在另一个终端运行 “trap” 指令查看被跟踪的信号,还可以通过 “trap - sig_num” 去掉对指定信号的跟踪?但是我没成功。。。
trap "echo trap signal 2" 2 # 跟踪信号2,即 SIGINT 信号;
echo Hello
sleep 5 # 在这期间,按 CTRL-C 后触发 trap 中的语句
exit 2 # 按正常流程执行完后并没有触发 trap 中的语句执行,不知道为啥
trap "echo trap signal 0 " 0 # 跟踪信号0
echo Hello
sleep 5 # 在这期间,按 CTRL-C 可以触发 trap 语句,但是按 CTRL-Z 不能触发 trap 语句
exit 0 # 按正常流程执行完,也触发了 trap 语句
trap "echo trap signal 0 " 0 # 可以触发
echo Hello
trap "echo trap signal 0 " 0 # 可以触发
echo Hello
exit 0
trap "echo trap signal 0 " 0 # 可以触发
echo Hello
exit 1 # 不管这里退出码是啥,好像都可以触发(因为只要执行没有出错就会产生信号0?)
# 以下代码中,正常执行完后会触发信号0
# 当代码结束前按 CTRL-C,会先触发信号2,再触发信号0
# 当代码结束前按 CTRL-Z, 两个信号都不会触发
trap "echo trap signal 0" 0
trap "echo trap signal 2" 2
echo Hello
sleep 5
# 试一试在这个代码执行时按 CTRL-C,每按依次,就会触发一次信号2,然后执行下一条语句,直到最后代码执行完成,还会触发一次信号0
trap "echo trap signal 0" 0
trap "echo trap signal 2" 2
cnt=0
while (( cnt < 10 ))
do
sleep 5
echo $cnt
(( cnt++ ))
done
# 在另外一个终端通过 kill -9 pid 命令依旧可以直接关闭以下脚本;
# 因为 trap 不能捕获到 SIGKILL信号 和 SIGSTOP信号;
trap "echo trap signal SIGKILL" SIGKILL SIGSTOP # 可以通过同一条 trap 语句同时接收多个信号
echo $$ # 输出当前脚本的 pid
cnt=0
while (( cnt < 10 ))
do
sleep 5
echo $cnt
(( cnt++ ))
done
# 按正常流程执行完以下代码,会在当前目录下新建一个 sig0.txt 文件;
# 在以下代码执行结束前按下 CTRL-C,会在当前目录下新建两个文件:sigint.txt 和 sig0.txt;
trap "touch sigint.txt; exit 1" SIGINT
trap "touch sig0.txt; exit" 0
sleep 5
# 正常流程执行完以下代码,会输出 get some signal
# 在以下代码执行结束前按下 CTRL-C,会输出两遍 get some signal
trap "echo get some signal" 0 SIGINT # 可以通过同一条 trap 语句同时接收多个信号
sleep 5
方式一:bash -x scriptName
方式二:在脚本的第一行(必须首行)写上 “#!/bin/bash -x” (从这里也可以看出,第一行不仅仅是写给程序员看的,也是有实际功能的)
在脚本的任意位置写上 “set -x”,代码执行到这里开始将进入调试状态;直到文件结束或者遇到 “set +x” 语句使得调试功能关闭;
#!/bin/bash -x
sleep 2
echo Hello
#!/bin/bash
set -x
sleep 2
set +x
echo Hello