本文记录Linux Shell编程中常用基本知识,方便快速入门以及查询使用。
本文主要分为以下几个部分:
一、Shell中的变量
任何编程语言中,有关变量的定义,作用范围,赋值等都是最最基础的知识。
0、默认变量
首先介绍几个shell中的默认变量。
变量 | 含义 |
---|---|
$0 | 当前脚本名称 |
$1 | 脚本接收的第一个参数 |
$2 | 脚本接收的第二个参数 |
$# | 脚本接收的所有参数个数 |
$@ | 脚本接收的所有参数 |
$* | 脚本接收的所有参数 |
$? | 前一行命令的执行状态 |
示例如下:
hadoop@client:~$ bash default_var.sh a b c d
$0 ==> default_var.sh
$1 ==> a
$2 ==> b
$# ==> 4
$@ ==> a b c d
$* ==> a b c d
$? ==> 0
1、变量定义及赋值
hadoop@client:~$ v1=hello
hadoop@client:~$ echo $v1
hello
但是要注意的是,赋值处必须为一个整体,不能有空格。
hadoop@client:~$ v2=hello world
No command 'world' found, did you mean:
Command 'tworld' from package 'tworld' (universe)
world: command not found
想要包含空格,需要用单引号或双引号包围,如
hadoop@client:~$ v2="hello world"
hadoop@client:~$ echo $v2
hello world
hadoop@client:~$ v3='hello world'
hadoop@client:~$ echo $v3
hello world
2、单引号(')和双引号(")的区别
上面的示例中看到hello world
使用单引号或双引号包围再赋值给变量时,两者效果相同,但是其中的区别在哪里?
hadoop@client:~$ a="hello"
hadoop@client:~$ b="$a world"
hadoop@client:~$ echo $b
hello world
hadoop@client:~$ c='$a world'
hadoop@client:~$ echo $c
$a world
可以看到,单引号中的$a
保持原样输出。而双引号中的$a
会替换成其变量值。
3、`符号
这个符号在数字键1的左侧,与单引号很类似。但是其功能与单引号双引号都有不同。在该符号中的命令会被执行。
hadoop@client:~$ d=`date`
hadoop@client:~$ echo $d
Wed Dec 28 06:31:13 PST 2016
如果不想使用这个符号,可以用$()
替换
hadoop@client:~$ e=$(date)
hadoop@client:~$ echo $e
Wed Dec 28 06:31:48 PST 2016
4、命令行交互read
有时候我们希望在脚本运行时能根据用户的输入决定脚本后续执行逻辑,比如在安装插件的时候经常会让用户选择输入[N/Y]
的时候。
比如有一个脚本script_test.sh
read -p "Please input [Y/N]: " yn
if [ "$yn" == "N" -o "$yn" == "n" ]; then
echo "NO"
elif [ "$yn" == "Y" -o "$yn" == "y" ]; then
echo "YES"
fi
在运行时根据用户的输入决定if
分支的走向。运行结果如下
hadoop@client:~$ sh script_test.sh
Please input [Y/N]: y
YES
read
命令的使用形式为
read [-pt] variable
参数p:后面可以接提示符
参数t:后面可以接秒数
例如,
read -p "please input your name" -t 5 name
表示将输入内容赋值给变量name
,用户有5秒钟的输入时间。
5、定义变量类型declare
默认情况下,变量的赋值内容都是字符类型的。例如下面的代码,我们期望的是输出一个求和值,但是输出的是一个求和表达式。
hadoop@client:~$ sum=100+300+500
hadoop@client:~$ echo $sum
100+300+500
如果想要输出求和后的值,可以使用declare命令。
hadoop@client:~$ declare -i sum=100+300+500
hadoop@client:~$ echo $sum
900
declare
命令的使用形式如下:
declare [-aixr] variable
参数a:将variable定义为数组
参数i:将variable定义为整型(integer)
参数x:将variable设置成环境变量,类似于export的作用
参数r:variable为readonly类型,值不能被更改
二、Shell中的集合类型
1、数组(array
)
(1)数组定义和赋值
数组中的元素用括号包围,各元素之间用空格隔开。例如
hadoop@client:~$ array_name=(v0 v1 v2 v3)
可以重新设置指定元素的内容,如下所示
hadoop@client:~$ array_name[2]=v22
hadoop@client:~$ echo ${array_name[2]}
v22
(2)数组元素访问
输出该数组中所有元素:
hadoop@client:~$ echo ${array_name[*]}
v0 v1 v22 v3
hadoop@client:~$ echo ${array_name[@]}
v0 v1 v22 v3
数组元素下标从0开始,想要访问指定位置的元素,使用[]指定下标值,如下所示
hadoop@client:~$ echo ${array_name[0]}
v0
hadoop@client:~$ echo ${array_name[1]}
v1
hadoop@client:~$ echo ${array_name[3]}
v3
hadoop@client:~$ echo ${array_name[2]}
v2
hadoop@client:~$ echo ${array_name[4]}
(3)获取数组长度
获取数组长度使用如下命令
hadoop@client:~$ echo ${#array_name[@]}
4
hadoop@client:~$ echo ${#array_name[*]}
4
获取数组中单个元素的长度使用如下命令
hadoop@client:~$ echo ${#array_name[2]}
3
2、map
map
类型中存储的都是键值对。
在Shell中定义map
变量如下所示:
declare -A m=(["a"]="1" ["b"]="2")
输出所有的key
hadoop@client:~$ echo ${!m[@]}
a b
输出所有的value
hadoop@client:~$ echo ${m[@]}
1 2
输出指定key
对应的value
hadoop@client:~$ echo ${m["a"]}
1
hadoop@client:~$ echo ${m["c"]}
添加元素
hadoop@client:~$ m["c"]="3"
hadoop@client:~$ echo ${m["c"]}
3
map
中键值对的个数
hadoop@client:~$ echo ${#m[@]}
3
三、Shell中的字符操作
在任何语言中对字符串的操作都是非常频繁的。字符串的操作主要包括,字符串截取,字符串替换等。
接下来的示例中,都以字符串http://blog.csdn.net/dabokele
作为初始字符串。
str="http://blog.csdn.net/dabokele"
1、字符串删除
删除前面的http://
hadoop@client:~$ echo ${str#http://}
blog.csdn.net/dabokele
删除后面的dabokele
hadoop@client:~$ echo ${str%/dabokele}
http://blog.csdn.net
#
从前往后截取,%
从后往前截取。
示例中表示将符合的最短数据删除,如果使用两个#
,或者两个%
,则表示将符合的最长数据删除。
如下所示,匹配最短时,会将https://csdn.ne
删除,匹配最长时,会将全部字符删除。
hadoop@client:~$ echo ${str#http://b*e}
t/dabokele
hadoop@client:~$ echo ${str##http://b*e}
2、字符串截取
可以从字符串的指定位置开始截取,同时可以指定截取的位数,如下所示:
hadoop@client:~$ echo ${str:2} // 从第二位开始截取到最末尾,第一个字符下标为0
tp://blog.csdn.net/dabokele
hadoop@client:~$ echo ${str:2:3} // 从第二位开始顺序截取三个字符
tp:
hadoop@client:~$ echo ${str:(-6):3} // 从倒数第六位开始,截取三个字符,最后一个字符下标为-1
bok
3、字符串替换
将http
替换成HTTP
hadoop@client:~$ echo ${str/http/HTTP}
HTTP://blog.csdn.net/dabokele
- 使用一个斜杠(
/
)表示只替换第一个遇到的字符。 - 使用两个斜杠(
//
)则表示替换全部符合的字符。 - 使用
#
匹配以指定字符开头的字符串。 - 使用
%
匹配以指定字符开头的字符串。
hadoop@client:~$ echo ${str/e/E}
http://blog.csdn.nEt/dabokele
hadoop@client:~$ echo ${str//e/E}
http://blog.csdn.nEt/dabokElE
hadoop@client:~$ echo ${str/#h/H} // 匹配开头的那个h
Http://blog.csdn.net/dabokele
hadoop@client:~$ echo ${str/e/E}
http://blog.csdn.nEt/dabokele
hadoop@client:~$ echo ${str/%e/E} // 匹配最后那个E,前一个匹配中匹配的是net中的e
http://blog.csdn.net/dabokelE
4、字符串默认值
假设以下这个场景,如果变量name
没有赋过值,则给一个默认值default
,否则使用指定的值。
hadoop@client:~$ echo $name
hadoop@client:~$ echo ${name-default}
default
hadoop@client:~$ name="ckm"
hadoop@client:~$ echo ${name-default}
ckm
但是,如果已经将变量name设置成“”,则结果如下:
hadoop@client:~$ name=""
hadoop@client:~$ echo ${name-default}
如果变量内容为“”或者变量未初始化则给默认值,可以在-
前加个冒号,使用:-
hadoop@client:~$ name=""
hadoop@client:~$ echo ${name-default}
hadoop@client:~$ echo ${name:-default}
default
5、字符串拼接
字符串拼接如下所示
hadoop@client:~$ echo "aaa""bbb"
aaabbb
hadoop@client:~$ echo "aaa"$str
aaahttp://blog.csdn.net/dabokele
hadoop@client:~$ echo "aaa$str"
aaahttp://blog.csdn.net/dabokele
6、字符串长度
求字符串长度用#
操作,如下所示
hadoop@client:~$ echo ${#str}
29
7、字符串split成数组
在以空格为分隔符分割字符串成数组时操作最简单。
hadoop@client:~$ s="a b c d e"
hadoop@client:~$ a=($s)
hadoop@client:~$ echo ${a[*]}
a b c d e
hadoop@client:~$ echo ${a[2]}
c
所以,如果需要指定特定字符进行分割,而原字符串中又没有空格时,可以先将特定字符替换成空格,然后按照上述进行分割,如下所示,
hadoop@client:~$ s="a,b,c,d,e"
hadoop@client:~$ a=(${s//,/ })
hadoop@client:~$ echo ${a[*]}
a b c d e
hadoop@client:~$ echo ${a[2]}
c
如果字符串中本身已有空格,并且期望的分隔符不是空格,按如下方法进行分割。首先将IFS
变量替换成指定字符,分割后再将IFS
更新为原字符。
hadoop@client:~$ s="a b,c,d,e"
hadoop@client:~$ old_ifs="$IFS"
hadoop@client:~$ s="a b,c,d,e"
hadoop@client:~$ OLD_IFS="$IFS"
hadoop@client:~$ IFS=","
hadoop@client:~$ a=($s)
hadoop@client:~$ IFS="$OLD_IFS"
hadoop@client:~$ echo ${a[*]}
a b c d e
hadoop@client:~$ echo ${a[0]}
a b
hadoop@client:~$ echo ${#a[*]}
4
8、字符串包含
有时候需要判断字符串str1
中是否包含字符串str2
,使用=~
操作符。
str1="hello"
str2="ell"
if [[ $str1 =~ $str2 ]];then
echo "$str1 contains $str2"
fi
查看运行结果
hadoop@client~$ sh script_test.sh
hello contains ell
四、Shell中的控制结构
0、循环接收脚本参数shift
测试脚本如下:
echo '原始参数: ' $*
shift
echo 'shift后参数: ' $*
shift 2
echo 'shift 2后参数: ' $*
查看脚本运行结果
hadoop@client:~$ sh script_test.sh a b c d e f g
原始参数: a b c d e f g
shift后参数: b c d e f g // 移除第一个参数a
shift 2后参数: d e f g // 继续移除前两个参数b和c
1、条件表达式
(1)if … then
判断表达式是经常用到的。整体结构如下所示,其中if
和fi
是必须的,中间的elif
和else
是可选的。
if [ 判断条件1 ]; then
执行内容1
elif [ 判断条件2 ]; then
执行内容2
else
执行内容3
fi
a)算术运算符
算术运算符的使用格式如下
a=10
b=20
val=expr $a + $b
常用的算术运算符包括
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $a + $b 结果为 30。 |
- | 减法 | expr $a - $b 结果为 10。 |
* | 乘法 | expr $a \* $b 结果为 200。 |
/ | 除法 | expr $b / $a 结果为 2。 |
% | 取余 | expr $b % $a 结果为 0。 |
= | 赋值 | a=$b 将把变量 b 的值赋给 a。 |
== | 相等 | 用于比较两个数字,相同则返回 true。[ $a == $b ] 返回 false。 |
!= | 不相等 | 用于比较两个数字,不相同则返回 true。[ $a != $b ] 返回 true。 |
b)关系运算符
关系运算符的使用格式如下
a=10
b=20
$a -eq $b
常用的关系运算符包括
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 true。 |
-ne | 检测两个数是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge | 检测左边的数是否大等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
c)布尔运算符
常用的布尔运算符如下
运算符 | 说明 | 举例 |
---|---|---|
! | 非运算,表达式为 true 则返回 false,否则返回 true。 | [ ! false ] 返回 true。 |
-o | 或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
-a | 与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
d)字符串运算符
常用的字符串运算符如下
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回 true。 | [ $a = $b ] 返回 false。 |
!= | 检测两个字符串是否相等,不相等返回 true。 | [ $a != $b ] 返回 true。 |
-z | 检测字符串长度是否为0,为0返回 true。 | [ -z $a ] 返回 false。 |
-n | 检测字符串长度是否为0,不为0返回 true。 | [ -n $a ] 返回 true。 |
str | 检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。 |
e)文件测试运算符
常用的文件测试运算符如下
运算符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
-p file | 检测文件是否是具名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
(2)case … esac
case
表达式的使用格式如下
case $变量 in
"内容1")
程序1
;;
"内容2")
程序2
;;
*) #匹配其他所有情况
程序3
;;
esac
看一个示例,如果第一个参数为hello
,则打印hello world
。如果第一个参数是bye
,则打印bye bye
。如果是另外的情况,则输出该参数。
case $$1 in
"hello")
echo "hello world"
;;
"bye")
echo "bye bye"
;;
"*")
echo $1
;;
esac
运行结果
hadoop@client:~$ sh script_test.sh hello
hello world
hadoop@client:~$ sh script_test.sh bye
bye bye
hadoop@client:~$ sh script_test.sh hehe
hehe
2、循环表达式
(1)while do done, untile do done
while
循环的格式如下
while [ condition ]
do
程序
done
与while
循环相反的是until
循环。
while [ condition ]
do
程序
done
在while
循环中,当条件满足使,就执行其中的程序。而until
循环中,当条件不成立时就终止循环。
下面举例用两种循环来实现当输入为yes
时跳出循环。
a) while循环示例如下
while [ "$yn" != "yes" ]
do
read -p "Please input yes to stop: " yn
done
echo "Stop!"
运行结果
hadoop@client:~$ sh script_test.sh
Please input yes to stop: no
Please input yes to stop: no
Please input yes to stop: yes
Stop!
b) until循环示例如下
until [ "$yn" == "yes" ]
do
read -p "Please input yes to stop: " yn
done
echo "Stop!"
运行结果如下
hadoop@client:~$ sh script_test.sh
Please input yes to stop: no
Please input yes to stop: yes
Stop!
(2)for … do … done
for
循环的格式如下
for var in con1 con2 con3 ...
do
程序
done
下面这个示例循环打印输入参数列表
for arg in $*
do
echo $arg
done
运行结果如下
hadoop@client:~$ sh script_test.sh a b c d e
a
b
c
d
e
(3)for … do … done的另一种形式
for
循环的另一种个数如下
for ((初始值; 目标值; 步长))
do
程序
done
循环输出1
到10
中的奇数
for ((i=1; i<=10; i=i+2))
do
echo $i
done
运行结果如下
hadoop@client:~$ bash script_test.sh
1
3
5
7
9
五、Shell中的函数
在Shell中也可以像其他编程语言那样,将代码块封装成函数。Shell中的函数,需要注意以下两点:
1、函数定义
由于Shell是从上往下执行的,所以在定义函数之前就调用该函数的话,会提示command not found
,例如
echo "Before :" `printFunc`
function printFunc() {
echo "print function !"
}
echo "After :" `printFunc`
运行结果如下,前一个函数调用处提示command not found
hadoop@client:~$ sh script_test.sh
2_script_test.sh: line 3: printFunc: command not found
Before :
After : print function !
2、函数参数与shell参数
在Shell中定义的函数,是可以传递和接收参数的。在子函数中,$1
表示接收的第一个参数…,这里需要注意与shell参数的区别。
但是测试后发现$0
表示的仍然是shell名称。如果想要显示当前函数名,可以使用$FUNCNAME
参数,如下所示,
function printStr() {
echo "printStr function print: $FUNCNAME"
echo "printStr function print: $*"
}
echo "main print: $0"
echo "main print: $*"
printStr f1 f2 f3
运行结果如下,
[hadoop@client ~]$ sh script_test.sh m1 m2 m3
shell name : script_test.sh
shell params : m1 m2 m3
function name : printStr
function params : f1 f2 f3
六、vi快捷操作
下面操作中出现大写字母,比如G
表示需要同时按住Shift
和G
键。n
表示输入的数字。其他比如text
则表示字符串。
1、跳转
按键 | 说明 |
---|---|
gg | 跳转到第一行 |
G | 跳转到最后一行 |
ngg / nG | 跳转到最后一行 |
Ctrl+f | 向下翻页 |
Ctrl+b | 向上翻页 |
h | 光标左移 |
j | 光标下移 |
k | 光标上移 |
l | 光标右移 |
w | 移到下一个单词的开头 |
W | 移到下一个单词的开头,忽略标点 |
b | 移到上一个单词的开头 |
B | 移到上一个单词的开头,忽略标点 |
e | 移到下一个单词的末尾 |
E | 移到下一个单词的末尾,忽略标点 |
nw/nW/nb/nB | 跳转n个单词 |
L | 移到当前屏幕最后一行 |
M | 移到当前屏幕中间行 |
$ | 到当前行最后一个字符 |
^ | 到当前行第一个字符 |
0 | 到当前行第一个字符 |
n | |
( | 到句子开头 |
) | 到句子结尾 |
{ | 到段落开头 |
} | 到段落结尾 |
2、查找和替换
(1)查找
按键 | 说明 |
---|---|
/text | 向后查找text字符 |
?text | 向前查找text字符 |
n | 跳转至下一个text字符 |
N | 跳转至上一个text字符 |
:set ic | 查找时忽略大小写 |
:set noic | 查找时对大小写敏感 |
(2)替换
按键 | 说明 |
---|---|
:s/oldtext/newtext/ | 替换当前行第一个oldtext为newtext |
:s/oldtext/newtext/g | 替换当前行所有oldtext为newtext |
:m,ns/oldtext/newtext/ | 在m行到n,用newtext替换第一个oldtext |
:1,$s/oldtext/newtext/ | 在1行到最后一行,用newtext替换第一个oldtext |
:m,ns/oldtext/newtext/g | 在m行到n,用newtext替换oldtext |
:1,$s/oldtext/newtext/g | 在1行到最后一行,用newtext替换oldtext |
在最后输入一个c
(confirm),表示替换前弹出确认提示。按y
则逐一替换当前光标处匹配的字符,n
则跳过当前光标处字符,a
替换全部匹配的字符。
3、复制、删除、撤销、重复
(1) 复制
按键 | 说明 |
---|---|
yy | 复制当前行 |
nyy | 复制当前及向下n行 |
p | 将复制内容黏贴到下一行 |
P | 将复制内容黏贴到上一行 |
另外,输入y+跳转
中的操作,可以为复制指定方向。比如yw
,从当前位置复制到下一个单词的开头。这样可以进行复制一个单词的操作。
(2)删除
按键 | 说明 |
---|---|
dd | 删除当前行(按p可黏贴) |
ndd | 从当前行向下删除n行 |
dG | 从当前行删除到最后一行 |
dgg | 从当前行删除到第一行 |
:n,md | 从第n行删除到第m行 |
x | 删除当前字符 |
nx | 删除当前向后n个字符 |
X | 删除光标前的字符 |
nx | 删除当前向前n个字符 |
另外,输入d+跳转
中的操作,可以为删除指定方向。比如dw
,从当前位置删除至下一个单词的开头。
(3)撤销
按键 | 说明 |
---|---|
u | 撤销上一次操作 |
U | 撤销当前行所有操作 |
. | 重复最后一次操作 |
(4)重复
按键 | 说明 |
---|---|
. | 重复最后一次操作 |
4、其他
按键 | 说明 |
---|---|
J | 将下一行连接到本行末尾 |
nJ | 将下n行连接到本行末尾 |
~ | 将当前字符切换大小写 |
n~ | 将当前向后n个字符切换大小写 |
~ | 将当前字符切换大小写 |
g~~ | 切换当前行大小写 |
u | 列编辑模式下,选中列转换成小写 |
U | 列编辑模式下,选中列转换成大写 |
guu | 当前行转换成小写 |
gUU | 当前行转换成大写 |
guw | 当前单词转换成小写 |
gUw | 当前单词转换成大写 |
5、列编辑
按Ctrl + v,进入列编辑模式。
(1)删除列
进入列编辑模式,
移动光标,选中需要删除的列,
按d,则会删除选中内容。
(2)插入列
进入列编辑模式,
移动光标选中需要插入内容的列。
按shift + i,会在选中列的第一行输入想要插入的内容。
连续按两次ESC,则会在选中的列处全部插入输入字符。