Shell #
shell 是 Linux 的命令解释器,解释用户对操作系统的操作。CentOS 默认使用的 shell 是 bash。例如,执行 ls
命令时,会先被 shell 对命令和参数进行解释,再交给内核执行。
/bin/bash
的位置是用于配置登录后的默认交互命令行的,不像 Windows,登录进去是界面,其实就是 explorer.exe
。而 Linux 登录后的交互命令行是一个解析脚本的程序,默认是 /bin/bash
。
执行命令的方式:
bash ./test.sh
创建一个子进程来运行test.sh
./test.sh
创建一个子进程来运行test.sh
,需要Sha-Bang
,也就是脚本文件开头的#!/bin/bash
来判断使用哪种 shell。source ./test.sh
在当前进程运行test.sh
。也就是说test.sh
里面执行的命令会影响当前 bash 进程。比如test.sh
里面只有一行命令为cd /var
,当前进程会切换到/var
。如果是bash ./test.sh
当前 bash 就不会切换目录,只是在子进程切换了目录。. test.sh
.
是 source 的另一种写法。
如果希望脚本对当前运行环境产生影响,就使用 source
来执行。
Linux 启动过程 #
- BIOS (基本输入输出系统)引导,在主板上执行,选择引导介质
- MBR 硬盘引导
- BootLoader(grub) 启动和引导 Linux 内核,确定内核版本
- kernel
- systemd
- 系统初始化
- shell
管道 #
管道是进程间通信的一种方式。|
管道符,可以将前一个命令的结果传递给后面的命令。
[root@pooky ~]# cat | ps -f
UID PID PPID C STIME TTY TIME CMD
root 22804 22792 7 21:52 pts/4 00:00:00 -bash
root 22848 22804 0 21:53 pts/4 00:00:00 cat
root 22849 22804 0 21:53 pts/4 00:00:00 ps -f
管道符会给前后两个命令分别创建子进程,上面的示例 22848 进程对应 cat
,22849 进程对应 ps -f
。管道符会把第一个进程的输出和第二个进程的输入建立连接。
打开另一个终端查看进程信息:
[root@pooky ~]# cd /proc/22848
[root@pooky 22848]# cd fd
[root@pooky fd]# ls -l
total 0
lrwx------. 1 root root 64 Sep 2 21:54 0 -> /dev/pts/4 # stdin pts 表示图形终端
l-wx------. 1 root root 64 Sep 2 21:54 1 -> pipe:[14225010] # stdout pipe 表示管道
lrwx------. 1 root root 64 Sep 2 21:53 2 -> /dev/pts/4
如果第二个进程是一个长时间运行的命令,那么对应的 fd 目录下的 0 ,也就是 stdin,也会指向 pipe:[14225010]
。
注意,管道符会创建子进程来执行命令,也就是说,如果在管道符前后执行 cd
某个目录,父进程是不会切换目录的。
重定向 #
- 一个进程运行时,会默认打开标准输入(一般是通过键盘,终端进行输入),标准输出,标准错误输出(默认输出到终端)三个文件描述符。
- 重定向符号其实是把输入和输出和文件建立连接。用文件来代替输入或输出。
输入重定向 #
<
,wc -l | /etc/passwd
统计/etc/passwd
内容的行数。
[root@pooky ~]# read var2 < a.txt
[root@pooky ~]# echo $var2
将 a.txt
内容读入到变量 var2
。
输出重定向 #
>
,将输出到文件前会先清空文件内容。echo 123 > /path/to/a/file
。>>
,将输出追加到文件。2>
,错误重定向,将错误输出到指定文件。&>
,标准输出和错误输出到指定文件。2>&1
,把 标准错误输出 重定向到 标准输出,ls a.txt b.txt 1>file.out 2>&1
的正确输出和错误输出都重定向到了file.out
文件中。1>&2
,把 标准输出 重定向到 标准错误输出
变量 #
变量赋值:
a=123
,=
左右不能有空格。let a=10+20
,变量可以进行计算。r=$(ls -l /etc)
,$()
可以得到命令结果。` `
反引号也可以得到命令结果。- 如果变量值有空格或者特殊字符,可以包含在
""
或者''
中。
默认值,test2=${2-_}
,-
好表示当 $2
为空值时,使用 _
为值。
变量引用:
${变量名}
,有些情况下也可以使用$变量名
。
变量作用范围:
- 变量的默认作用范围是当前 shell 进程。
[root@pooky ~]# a=1
[root@pooky ~]# bash # 进入一个新的 shell 子进程
[root@pooky ~]# echo $a # a 变量的值是空的
[root@pooky ~]# a=2
[root@pooky ~]# exit
exit
[root@pooky ~]# echo $a # a 变量的值不会被子进程的赋值改变
1
[root@pooky ~]# bash test.sh # bash 打开了一个新的 shell 进程,在 test.sh 中的变量不会作用到父进程
[root@pooky ~]# source test.sh # 可以使用 source 命令,在当前进程执行 test.sh,使变量在父进程生效
export
导出变量,export
设置环境变量是暂时的,可以被子进程读取到,比如在一个终端 export 的变量,在这个终端打开的 shell 子进程都可以读取到这个变量。unset
删除变量,unsert a
环境变量 #
环境变量,是每个 shell 都可以读取到的变量:
env
查看所有环境变量,env | more
。set
也可以查看环境变量。-e
传回值不等于 0,则立即退出 shell。-u
执行脚本的时候,如果遇到不存在的变量,Bash 默认忽略它。-x
默认情况下,脚本执行后,只显示运行结果,这个参数可以显示执行的指令及参数。-o pipefail
-e
参数不适用于管道命令。只要管道符最后一个子命令不失败,管道命令总是会执行成功如foo
是一个不存在的命令,但是foo | echo $a
这个管道命令会执行成功。-o pipefail
用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。
$?
上条命令的返回值,不为 0,表示执行失败。$$
表示当前进程 PID$0
当前进程的名称$PATH
命令路径$PS1
[root@pooky ~]# echo $?
0
[root@pooky ~]# echo $$
32163
[root@pooky ~]# echo $0
-bash
位置变量:
$1
$2
…$n
分别表示第一个参数,第二个,到第 n 个参数。注意第十个参数,要用${10}
,否则$
会默认和1
结合。
test.sh
:
#!/bin/bash
echo "文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";
[root@pooky ~]# ./test.sh 1 2 3
文件名:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3
环境变量配置文件 #
/etc/profile
/etc/profile.d
~/.bash_profile
~/.bashrc
/etc/bashrc
执行顺序从上到下。
/etc
目录下的文件对系统中所有用户都永久有效。~
目录下的文件只对某个用户永久有效。
配置文件分为 bashrc
和 profile
两种,因为登录用户分为两种,login shell 和 no login shell。比如 su - user1
切换了用户和 home 目录,这属于 login shell。login shell 会执行上面所有的配置文件。如果使用 su user1
,那么只会执行两个 bashrc 文件。
修改可上面的配置文件可以使用 source <filename>
使其立即生效。
数组 #
定义数组 IPS=( 10.0.0.1 10.0.0.2 10.0.0.3)
。
[root@pooky ~]# IPS=( 10.0.0.1 10.0.0.2 10.0.0.3)
[root@pooky ~]# echo ${IPS[0]} # 显示执行下标的元素
10.0.0.1
[root@pooky ~]# echo ${IPS[1]}
10.0.0.2
[root@pooky ~]# echo ${IPS[2]}
10.0.0.3
[root@pooky ~]# echo ${IPS} # 显示的是数据第一个元素
10.0.0.3
[root@pooky ~]# echo ${IPS[@]} # 显示数组所有元素
10.0.0.1 10.0.0.2 10.0.0.3
[root@pooky ~]# echo ${#IPS[@]} # 显示数据长度
3
转义和引用 #
特殊字符:
#
注释;
分号\
转义符\n
\r
\t
字母转义\$
\"
\\
非字母转义
"
'
引号
引用:
- 双引号,不完全引用,中间的内容会进行解释。
- 单引号,完全引用,单引号中间是什么就显示什么,不进行解释。
- 反引号,可以得到命令结果。和
$()
的功能一样。
[root@pooky ~]# var1=123
[root@pooky ~]# echo "$var1"
123
[root@pooky ~]# echo '$var1'
$var1
运算符 #
expr
可以用来计算expr 4 + 5
,但是只支持整数。- 正常的变量赋值,
=
右边都被当做字符串,如果右边需要计算可以使用let
,(())
是let
的简化。
[root@pooky ~]# ((a=4+5))
[root@pooky ~]# echo $a
9
[root@pooky ~]# b=4+5
[root@pooky ~]# echo $b
4+5
[root@pooky ~]# ((a++))
[root@pooky ~]# echo $a
10
特殊符号 #
- 引号
""
''
- ``
- 括号
()
打开一个子进程,或者数组定义$()
(())
执行算数运算[]
[[]]
测试,获取数组下标元素[ 5 -gt 4 ]
,[[]]
就可以使用比较运算符[[ 5 > 4 ]]
<>
输入输出重定向{}
输出范围echo {0..9}
,会输出0 1 2 3 4 5 6 7 8 9
- 运算和逻辑符号
+
-
*
/
%
<
>
=
&&
||
!
- 转义符号
\
- 其他符号
#
注释;
命令分隔符,;;
case 语句的分隔符要转义:
空指令.
和 source 命令相同~
home 目录,
分隔目录-
回到上次访问的目录,cd -
*
通配符,匹配 0 或多个字符?
条件测试,通配符 匹配任意一个字符$
取值符号|
管道&
后台运行_
空格
判断 #
$?
得到命令的执行结果,判断是否为 0,非 0 为不正常退出。使用exit
来退出程序。exit
会返回出错命令的错误码,也可以自定义错误码exit <code>
。test
命令检查文件或者比较值。测试语句可以简化为[]
。[]
的扩展写法[[]]
支持&&
||
<
>
。test <表达式>
简化为[ 表达式 ]
。常用的测试:- 字符串测试
[ str1 = str2 ]
[ str1 != str2 ]
[ -z str ]
-z
表示字符串长度是否为 0 - 整数测试
[ int1 -eq int2 ]
-eq
表示等于,-ge
大于等于,gt
大于,le
小于等于,lt
小于,-ne
不等于。[]
做整数判断时,只能使用-eq
这种形式。想要使用逻辑运算符<
等,要使用[[]]
。 - 文件测试,
[ -e file]
-e
文件是否存在,-d
文件存在并且是目录,-f
文件存在并且是普通文件,-b
文件存在并且是块文件,-c
文件存在并且是字符设备文件,-x
文件存在并且可执行。
- 字符串测试
[root@pooky ~]# test -f /etc/passwd
[root@pooky ~]# echo $?
0
[root@pooky ~]# test -f /etc/passwd2
[root@pooky ~]# echo $?
1
[root@pooky ~]# [ -d /etc/ ]
[root@pooky ~]# echo $?
0
[root@pooky ~]# [ -e /etc/ ]
[root@pooky ~]# echo $?
0
[root@pooky ~]# [ 5 -gt 4 ]
[root@pooky ~]# echo $?
0
[root@pooky ~]# [[ 5 -gt 4 ]]
[root@pooky ~]# echo $?
0
[root@pooky ~]# [ "aaa" = "aaa" ]
[root@pooky ~]# echo $?
0
if #
if-then
语句:
if [ 测试条件成立或执行命令返回值为 0 ]
then
执行对应的命令fi
结束
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
if-else
语句:
if [ 测试条件成立 ]
then
执行对应的命令else
测试条件不成立,执行对应的命令fi
结束
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then
echo "true";
else
echo "false"
fi
if-elif-else
语句:
if [ 测试条件成立 ]
then
执行对应的命令elif [ 测试条件成立 ]
then
执行对应的命令else
测试条件不成立,执行对应的命令fi
结束
a=10
b=20
if [ $a == $b ]; then
echo "a 等于 b"
elif [ $a -gt $b ]; then
echo "a 大于 b"
elif [ $a -lt $b ]; then
echo "a 小于 b"
else
echo "没有符合的条件"
fi
嵌套 if
语句:
if [ 测试条件成立 ]
then
执行对应的命令if [ 测试条件成立 ]
then
执行对应的命令fi
fi
结束
a=10
b=20
c=30
if [ $a == $b ]; then
echo "a = b"
if [ $a = $c ]; then
echo "a = c"
fi
fi
分支 #
case "$变量" in
"var1" )
echo "var1" ;;
"var2" )
echo "var2" ;;
"var3"|"var4" )
echo "var3|var4" ;;
* ) # 匹配其他情况
echo "*" ;;
esac
循环 #
for #
for 参数 in 列表
do
执行对应的命令done
封闭一个循环
使用 {}
得到列表:
for i in {1..9}
do
echo $i
done
# or
for i in {1..9}; do echo $i; done
# or
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
使用命令得到列表,如把当前目录后缀为 txt 的文件改为后缀为 text:
for filename in `ls *.txt`
do
mv $filename $(basename $filename .txt).text
done
basename
命令可以得到文件名,不包含后缀名。
C 风格的 for 循环:
for((i = 1; i <= 5; i++))
do
echo "$i"
done
while #
while test测试是否成了
do
执行对应的命令done
int=1
while (( $int<=5 ))
do
echo $int
let "int++"
done
# or
a=50
while [ "$a" -le 100 ]
do
echo $a
((a++))
done
# 死循环
while :
do
echo "."
done
until #
和 while 相反,循环条件为 false 时执行,为 true 时停止。
a=0
until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done
嵌套循环 #
break
和 continue
语句:
for filename in /etc/profile.d/*.sh
do
echo $filename
if [ -x $filename ]; then
. $filename
fi
done
# or
for num in {1..9}
do
if [ $num -eq 5 ]; then
break # 退出循环
# continue 跳过当前循环
echo $num
fi
done
使用循环处理命令行参数 #
- 命令行参数可以使用
$1
$2
..$n
来获取。 $0
表示当前脚本名称$*
和$@
代表所有位置参数。只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。$#
传递到脚本的参数个数
for pos in $*
do
if [ $pos = help ];then
fi
done
# or
while [ $# -ge 1 ];
do
echo $#
echo "do something"
shift # 删除第一个参数
done
函数 #
定义函数:
function funcname() {
echo "do something"
echo $1
echo $2
echo ${10}
return 0
}
执行函数:funcname
。
函数作用范围的变量:local 变量名
函数的参数:$1
$2
.. $n
返回值:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return 后跟数值 n (0-255)
函数库 #
系统函数库:/etc/init.d/functions
导入系统函数库,执行函数 echo_success
:
[root@pooky init.d]# source /etc/init.d/functions
[root@pooky init.d]# echo_success
[root@pooky init.d]# [ OK ]
自定义函数库:使用 source
导入脚本中的函数