1、man
manual 是类 UNIX 系统的标准手册工具,共 9 卷,用 man man
可以看到 9 卷分别是什么内容:
1 Executable programs or shell commands 可执行程序或shell命令
2 System calls (functions provided by the kernel) 系统调用
3 Library calls (functions within program libraries) 库调用
4 Special files (usually found in /dev) 特殊文件
5 File formats and conventions eg /etc/passwd 文件格式和约定
6 Games 游戏
7 Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) 杂项(包括宏和惯例)
8 System administration commands (usually only for root) 系统管理命令(通常仅适用于root用户)
9 Kernel routines [Non standard] 内核例程(非标准)
如果只有一个含义,直接查询即可,比如man ls
,但有很多是一词多义的:chmod, chown, chmod, stat, exit, info, socket, flock, open, write, printf, passwd
等等。
man 1 printf
表示 shell 命令,
man 3 printf
表示 C 语言标准库函数,
可以用 -a 选项显示全部,比如 man -a printf
,按 q
退出之后,可以按回车键表示继续看下一卷,或按 Ctrl-C
直接退出。
2、#!/bin/bash
#!/bin/bash
放在 shell 脚本开头,是指此脚本使用/bin/bash
来解释执行。
#!/usr/bin/python
也是类似的, 表示用/usr/bin/python
来解释执行 python 脚本,加了这行就可以用./xx.py
的方式来运行了。
#!/usr/bin/env python
会去环境变量路径中寻找 python 目录。
3、shell 运行方式
(1) bash 1.sh
创建子进程运行 1.sh,需要有 r
权限。
(2) ./1.sh
创建子进程运行 1.sh,需要有 r
和 x
权限。
(3) source 1.sh
当前进程运行 1.sh,需要有 r
权限,
(4) . 1.sh
点命令是 source
命令的另一种写法。
如何证明是在当前进程运行,还是创建子进程运行呢,下面用 2 种简单的方法:
(1) 创建一个子目录,然后用 cd
命令
~/test$ mkdir child
~/test$ echo 'cd child;pwd' > 1.sh
使用 bash
执行结束后,可以看左边的提示符,当前的工作目录没有改变,cd
命令只对子进程产生影响:
~/test$ bash 1.sh
/home/oo/test/child
~/test$
使用 source
执行结束后,当前目录变成了 child 目录,因为 cd
命令对当前进程产生了影响:
~/test$ source 1.sh
/home/oo/test/child
~/test/child$
(2) 变量赋值
bash
是创建子进程来执行脚本,所以子进程的变量,在父进不能访问;
而source
在当前进程执行,所以脚本中对变量的赋值,当前进程能访问:
$ echo "a=99" > 1.sh
$ bash 1.sh
$ echo $a
$ source 1.sh
$ echo $a
99
4、内部命令,外部命令
通过 type
命令可以查询某个命令是内部命令、外部命令,还是别名(alias
):
$ type cd
cd is a shell builtin
$ type find
find is /usr/bin/find
$ type ls
ls is aliased to `ls --color=auto'
如果用 bash shell,可以通过help
命令列出所有内部命令列表。
区别:
(1)内部命令不创建子进程,对当前进程有影响,执行速度快;
外部命令不在shell中,保存在一个独立的文件中,会创建新的进程来执行。
(2)内部命令查询帮助信息用:help 内部命令,比如 help cd
,
外部命令查询帮助信息用:外部命令 --help,比如 ls --help
。
当然除了 man
、help
之外,info
也可以用于查询帮助信息。
5、重定向
(1) 输入重定向 <
$ wc -l < /etc/passwd
32
$ echo 123 > 1.txt
$ read var < 1.txt
$ echo $var
123
(2) 输出重定向
① > 先清空该文件,再追加内容。
② >> 文件的内容不变,在文件末尾追加内容。
③ 2> 错误重定向
$ nothing
nothing: command not found
$ nothing 2> 1.txt
$ cat 1.txt
nothing: command not found
④ &> 表示无论是正确还是错误,都输出重定向。
(3) 输入、输出重定向的组合使用
将开始标记 和结束标记之间的内容作为 cat
的输入,cat
的输出重定向到文件 1.txt:
$ cat > 1.txt << EOF
> 123
> 456
> EOF
$ cat 1.txt
123
456
这里的 EOF 只是开始和结尾的 tag,也可以换成其他的,比如ABCD:
$ cat > 1.txt << ABCD
> 1234
> 5678
> ABCD
$ cat 1.txt
1234
5678
6、变量
(1) 赋值, =
的两边不能有空格,否则被解释为命令:
$ a=123
$ echo $a
123
$ a =123
a: command not found
$ a= 123
123: command not found
(2) 算数运算用 let
,否则默认是字符串
$ a=1+2+3
$ echo $a
1+2+3
$ let a=1+2+3
$ echo $a
6
(3) 把命令赋值给变量,意义不大:
$ a=ls
$ $a -l
(4) 把命令的结果赋值给变量:
$ a=$(find . -name *.sh)
$ echo $a
(5) 作用范围
变量只在当前 shell 生效,子shell、父shell 都不能共用。
使用 export
命令之后,子shell 可以获取 父shell 的变量。其他终端不可以。
(6)变量不用了之后,可以删除: unset
变量名。
7、环境变量
(1)env
命令可以列出所有环境变量,一般是大写。
PATH
指定命令搜索路径,
PS1
可以使当前终端显示更友好,
UID
用户ID,
USER
用户名。
PWD
当前路径,
LD_LIBRARY_PATH
、LD_PRELOAD
程序运行时,搜索动态库路径。
(2) set
命令可以列出系统中已经存在的 shell 变量,比 env
命令列出的变量更多。
$ abcde=1234567
$ set | grep abcde
abcde=1234567
(3) 预定义变量
$$
:当前进程的PID号
$0
:当前程序或脚本的名称
$?
:用于返回上一条命令是否成功执行。如果成功执行,将返回数字0,否则返回非零数字(通常情况下都返回数字1)。
$*
或 $@
:保存传递给脚本或进程的所有参数
$#
:脚本参数的个数,(类似C语言的 argc)
$1
、 $2
、 ${10}
等等 :位置变量,从命令行给脚本传参(类似C语言的argv[]),还有函数传参时会使用。
(4) 环境变量的配置文件
/etc/profile
是所有用户共用的。
~/.bashrc
是某一个用户特有的。
如果存在重复定义的变量,/etc/profile
先执行,会被后执行的 ~/.bashrc
覆盖。万一有变量被重复定义了,执行顺序很重要,可以在这 2 个文件中添加echo
输出重定向到文件,来判断谁先被执行。
另外:
su root
是 non-login shell,不会加载所有的配置文件,不建议使用;
su - root
是 login shell,会加载所有配置文件,推荐使用这种方式。
8、数组
定义数组用小括号,例如: a=(1 2)
,
访问所有元素 ${a[@]}
,
数组元素的个数 ${#a[@]}
,
访问单个元素 ${a[0]}
,负号表示倒数第几个 ${a[-1]}
。
$ a=( 11 22 33 )
$ echo ${a[@]}
11 22 33
$ echo ${a[*]}
11 22 33
$ echo ${#a[@]}
3
$ echo ${a[0]}
11
$ echo ${a[1]}
22
$ echo ${a[-1]}
33
$ echo ${a[-3]}
11
9、特殊字符
#
注释
;
分号用来分隔两条命令。
` 反引号 与 $() 都是用来执行命令的。
"
双引号(不完全引用)会对变量解释,
'
单引号(完全引用)不解释变量。
$ a=hello
$ echo "$a"
hello
$ echo '$a'
$a
10、整数运算
默认会解释为字符串,所以需要用 expr
,注意空格:
$ expr 1 + 2 + 3
6
$ expr 1+ 2 + 3
expr: syntax error
除了 + 还有 - * / % > < 等等,注意转义字符:
$ expr 1 - 2
-1
$ expr 6 \* 6
36
$ expr 30 / 6
5
$ expr 32 % 3
2
$ expr 3 \> 4
0
$ expr 3 \< 4
1
$ expr 3 \<= 3
1
$ expr 3 \!= 3
0
$ expr 3 \!= 4
1
不支持小数运算:
$ expr 5 + 0.5
expr: non-integer argument
另外 length 可以计算字符串长度:
$ expr length "hello"
5
**
表示乘方,expr
没有乘方功能:
$ let a=2**3
$ echo $a
8
let
简写为双圆括号:
$ ((a=5+6))
$ echo $a
11
11、园括号()
里面的赋值,不会影响外面。
$ a=10;echo $a
10
$ (a=20;echo $a)
20
$ echo $a
10
是圆括号()创建了一个子进程吗,还是其他原因呢?
可以做一个小实验,先打开 2 个终端:
先查看有几个 bash
进程,可以看到是 5 个:
$ ps aux |grep bash |wc -l
5
$ (a=20;echo $a;sleep 9)
把圆括号睡眠一下,趁这个时间再次查看 bash
数量,比刚才多了 1 个,等 sleep
结束之后再看 bash
数量又恢复 5个,说明圆括号()确实创建了子进程:
$ ps aux |grep bash |wc -l
6
另外也可以再通过 cd
命令来看,圆括号中改变了工作目录,并不影响父进程:
$ pwd
/home/oo/test
oo@uu:~/test$ (a=20; cd ..; pwd)
/home/oo
oo@uu:~/test$ pwd
/home/oo/test
12、test
命令
test
命令是通过设置 $?
的值, 来让我们可以判断是否正常退出,或者条件是否为真,0 为 真,非 0 为假。
test
通常有三类测试:字符串、数字、文件,可以通过 man
查询具体用法:
man test|grep INTEGER
man test|grep STRING
man test|grep FILE
通过 man test
可以看到 test EXPRESSION
和 [ EXPRESSION ]
两种写法,
有时为了程序美观,可以把 test
换成方括号[ ]
的写法。
(1) 字符串
字符串是否相等,会区分大小写:
$ [ "abc" = "abc" ]
$ echo $?
0
$ [ "abc" = "a" ]
$ echo $?
1
(2) 整数
[]
扩展为 [[]]
,可以支持更多运算符。
$ test 3 -gt 4
$ echo $?
1
$ [ 3 -gt 4 ]
$ echo $?
1
$ [[ 3 > 4 ]]
$ echo $?
1
(3) 文件
文件是否存在:
$ test -f /etc/passwd
$ echo $?
0
$ test -f /etc/passwd.1234
$ echo $?
1
目录存在:
$ test -d /etc
$ echo $?
0
文件或目录存在:
$ test -e /etc
$ echo $?
0
$ test -e /etc/passwd
$ echo $?
0
13、大括号
(1) 表示某个范围内的值,主要是给 for
循环使用的
$ echo {0..3}
0 1 2 3
(2) 减少重复书写
cp -v /etc/passwd{,.bak}
相当于 cp -v /etc/passwd /etc/passwd.bak
14、if
(1)if-then-fi
(2)if-then-else-fi
(3)if-then-elif-then-else-fi
let a=1-6
if [ $a -eq 0 ];
then
echo "a=0"
elif [ $a -lt 0 ];
then
echo "a<0"
else
echo "a>0"
fi
15、case-esac
a=3
case "$a" in
1)
echo "case 1"
;;
2|3)
echo "case 2 or 3"
;;
*)
echo "case *"
;;
esac
case 语句的例子在很多文件都有,比如 grep -n case ~/.bashrc
16、for
for i in {1..3}
do
echo $i
done
for filename in /etc/profile.d/*.sh
do
if [ -x $filename ]; then
source $filename;
echo "source $filename;";
fi
done
下面是 C 语言风格的写法:
for ((i=1;i<=3;i++))
do
echo $i
done
17、while
a=1
while [ $a -le 3 ];
do
echo $a;
((a++));
done
用冒号表示条件为真,写一个死循环:
while :
do
echo "always"
done
18、until
until 循环与 while 语法一致,只是条件为假才执行循环:
a=8
until [ $a -lt 5 ];
do
echo $a;
((a--));
done
19、break 与 continue
for n in {1..5}
do
if [ $n -eq 3 ];then
break;
fi
echo $n
done
for n in {1..5}
do
if [ $n -eq 3 ];then
continue;
fi
echo $n
done
20、字符串截取命令
basename
命令,可以用来删掉后缀名:
$ basename hello.c.diff .diff
hello.c
$ basename hello.c.diff .c.diff
hello
dirname
用于获取目录
$ dirname /etc/hosts
/etc
其实它们不在乎这个目录是否真实存在,只是简单的处理一下字符串而已:
$ dirname /1/2/3
/1/2
21、函数
函数定义的格式:
函数名(){ }
$ testfun(){ echo hello;};testfun;
hello
避免函数内部的变量影响到外面,使用 local
关键字限定作用域为该函数。