LInux Shell程序设计
将指定路径加到环境变量$PATH
PATH=$PATH:绝对路径
export PATH
一、变量
1.用户定义的变量
- 给变量赋值时,等号左右不能有空格
- $:访问变量
a=33
echo $a
只读变量
readonly a
公共变量
export a
显示变量
echo $a
清除变量
- set : 显示所有本地定义的变量
unset a
2.shell定义的变量
- 所有环境变量均为大写
设置环境变量
TEMPVAR=123
export TEMPVAR
显示环境变量
- 查看所有环境变量
env
清除环境变量
unset TEMPVAR
常用的shell环境变量
环境变量 | 含义 |
---|---|
HOME | 保存注册用户主目录的绝对路径名 |
PATH | 保存路径(冒号分隔) |
TERM | 终端类型 |
UID | 当前用户标识符 |
PWD | 当前工作目录的绝对路径 |
PS1 | 主提示符 特权用户:# 普通用户:$ |
PS2 | 辅助提示符:> |
IFS | 缺省域分隔符 空格、tab |
LOGNAME | 保存登录名 |
SHELL | 保存缺省shell /etc/passwd中设置 |
3.位置参数
$1:shell取第一个参数 从1开始
$0:表示当前执行shell程序名(不是位置参数)
4.预定义变量
- 不能重定义
预定义变量 | 含义 |
---|---|
$0 | 当前执行的进程名 |
$# | 位置参数的数量 |
$* | 所有位置参数的内容 |
$? | 命令执行后返回的状态 0:成功 1:失败 |
$$ | 当前进程的进程号 |
$! | 后台运行的最后一个进程号 |
5.参数置换的变量
- 中间没有空格
- shell中不能为位置参数赋值
a=${b-200}
如果参数b有值则a=b,否则a=200
a=${b=200}
如果参数b有值则a=b,否则a=200,b=200
a=${b?info}
如果参数b有值则a=b,否则显示 info并退出,如果Info缺省则显示标准信息
a=${b+200}
如果参数b有值,则a=200,否则不置换
二、shell中的引用
1.双引号 “”
- 不能消除 $ ,\ ,"",反引号`四种
2.单引号 ‘’
- 消除单引号里面所有特殊字符的含义
3.反引号 `
- 反引号里的内容作为系统命令
- 输出变量为命令执行的结果
4.反斜杠 \
- 屏蔽特殊含义
三、Shell脚本
- 第一行:#!/bin/bash ——使用bshell解释器
- 第二行:脚本名称
- 注释:#
1.eval
- eval args :将args组合成一个命令执行
2.exec
- 执行完退出进程
3.export
- 变量可以带到子shell
4.readonly
- 将shell变量标识为不可变
5.read
- 从标准输入设备读入一行,分解成若干字,赋值给shell程序内部定义的变量。
6.shift
- 在程序中每使用一次shift语句,都使所有的位置参数依次向左移动一个位置,并使位置参数“$#”减1,直到减到0。
7.wait
- shell等待在后台启动的所有子进程结束。wait的返回值总是真。
8.exit
- 退出shell程序
9.source(等价于.)
- 在当前shell环境下读入指定的shell程序文件并依次执行文件中的所有语句。
- source 文件名(可以不拥有可执行权限)
10.":"空命令
- 通常放在行的最左边,实际不作任何命令,只返回出口代码0
四、程序流程控制
1.测试命令
1. test "abc" = "def" #注意等号左右空格
2. [ "abc" = "def" ] #注意等号,括号左右空格
[ -z $HOME ] #真为0
=,!=,>,<
-z(字符串是否为空) 1:非空
-n(字符串是否非空)
文件测试
-e 文件名 :如果文件存在则为真
-r 文件名 :如果文件存在且可读则为真
-w 文件名 :如果文件存在且可写则为真
-x 文件名 :如果文件存在且可执行则为真
-s 文件名 :如果文件存在且非空则为真
-d 文件名 :如果文件存在且为目录则为真
-f 文件名 :如果文件存在且为普通文件则为真
-c 文件名 :如果文件存在且为字符型特殊文件则为真
-b 文件名 :如果文件存在且为块特殊文件则为真
逻辑操作符:
-a :逻辑与,操作符两边均为真,结果为真,否则为假。
-o :逻辑或,操作符两边一边为真,结果为真,否则为假。
! :逻辑否,条件为假,结果为真。
混合命令条件执行
使用&&:command1 && command2,这种命令执行方式相当地直接。&&左边的命令(command1)返回真(即返回0,成功被执行)后,&&右边的命令(command2)才能够被执行;
使用||:command1 || command2,||的作用有一些不同。如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2);或者换句话说,“如果这个命令执行失败了|| 那么就执行这个命令”>
command1 && command2 && command3,当command1, command2成功时才执行command3 >
command1 && command2 || comamnd3,仅当command1成功,command2失败时才执行command3
2.流控制
两个(())表示执行c语言代码
2.1 if
if 条件1
then 命令1
elif 条件2
then 命令2
else 命令3
fi
- if语句必须以单词fi
- 必须将then部分放在新行
#!/bin/bash
var=$1
if [ $var \< "9" ]
then
echo "less than 9"
else
echo "more than 9"
fi
2.2 case
case "字符串变量" in
值1)指令1...
;;
值2)指令2...
;;
......
*)指令3...
;;
esac
#!/bin/bash
var=$1
case $var in
1)
echo "1"
;;
2)
echo "2"
;;
*)
echo "others"
;;
esac
2.3 for
for 变量 in 列表;
do
循环体
done
#!/bin/bash
# sum of 1 to 100
Sum=0
for i in {1..100};
do
let Sum=$Sum+$i
done
echo "Sum is $Sum"
2.4 while
while 条件(或命令);
do
循环体
done
#!/bin/bash
# sum of 1 to 100
Sum=0
i=0
while ((i<10));
do
let Sum=$Sum+$i
let i=$i+1
done
echo "Sum is $Sum"
2.5 until 直到条件为真才停止循环
until [ 测试条件 ];
do
语句 #可以是多条语句
done
#!/bin/bash
# sum of 1 to 100
var=10
until [ $var -lt 1 ];
do
echo $var
var=$(($var-1))
done
练习1:cut
cut -d : -f 1 /etc/passwd | grep "user$i" 2>> /tmp/etc.err || sudo useradd user$i
##
cut:裁剪 -d:指定间隔符进行裁剪
1:裁剪的第一块
cut -d : -f 1 /etc/passwd:裁剪出第一列
2:表示出错
|:管道
||:逻辑或
grep "user$l":查找user i
##
#用:裁剪 /etc/passwd 内容取第一列 查找 useri 如果错误, 结果重定向到 /tmp/etc.err 如果执行不成功 增加用户 user1
练习2:乘法表
#!/bin/bash
for((i=0;i<=9;i++))
do
for((j=1;j<=i;j++))
do
let k=i*j
echo -n "$i*$j=$k"
done
#换行
echo " "
done
- echo -n: 不换行
算数运算
- let expr 只能计算整数
let
- 不用空格分开
- 变量计算不用$
expr
- 运算符两边要有空格
- *乘法要转义\*
- 外层要有反引号``
六、Shell命令分组
“()” 和 “{}”
一定注意{}里面的内容要有空格和分号
- 用(command_list;)将一组命令括起来,则这些命令会由子shell来完成,能保证所有的改变只对子进程产生影响,而父进程不受任何干扰.
#!/bin/bash
echo "test ()"
NUMBER=1
(A=2;B=2;NUMBER=`expr $A + $B`;echo $NUMBER)
echo $NUMBER
- {command_list;}则在当前shell中执行。"{}"用于将顺序执行的命令的输出结果用于另一个命令的输入。
#!/bin/bash
echo "test {}"
NUMBER=1
{ A=2;B=2;NUMBER=`expr $A + $B`;echo $NUMBER; }
echo $NUMBER
七、Shell信号
- kill –l 命令看到系统中全部可用的信号名
shell编程只需关心 1 2 3 15
- 1 SIGHUP 挂起或父进程被杀死
- 2 SIGINT 来自键盘的中断信号,通常是<CTRL-C>
- 3 SIGQUIT 从键盘退出
9 SIGKILL 无条件终止11 SIGSEGV 段(内存)冲突- 15 SIGTERM 软件终止(缺省杀进程信号)
1.trap 捕获信号
- trap ‘commands’ signal-list
#!/bin/bash
#trap
trap "my_exit" 2
LOOP=0
my_exit()
{
echo "You just hit <CTRL-C>,at number $LOOP"
echo "I will now exit"
exit 1
}
while :
do
LOOP=`expr $LOOP + 1`
echo $LOOP
done
信号2 SIGINT 来自键盘的中断信号
八、bash程序的调试
bash -x 程序名
- -n:测试shell脚本语法结构,只读取shell脚本但不执行
- -x:进入跟踪方式,执行命令时把命令和它们的参数显示出来
- -e:非交互方式,如果一个命令失败就立即退出
- -I:交互方式
- -k:从环境变量中读取命令的参数
- -r:限制方式,不能执行:cd, 更改PATH,指定全路径名,输出重定向操作
- -t 执行命令后退出(shell exits)
- -u 置换时把未设置的变量看作出错
- -v verbose,当读入shell输入行时把它们显示出来
1.-x -v
- 通常使用-x进行跟踪执行,执行并显示每一条指令。
- "-v"选择项使shell在执行程序的过程中,把它读入的每一个命令行都显示出来,
- "-x"选择项使shell在执行程序的过程中把它执行的每一个命令在行首用一个“+”加上命令名显示出来。并把每一个变量和该变量所取的值也显示出来。
- 主要区别在于: “-v"打印出命令行的原始内容,而”-x"则打印出经过替换后的命令行的内容
九、Linux函数库
- 静态函数库:在程序执行前就加入到目标程序中。是一个普通的目标文件的集合,.a为后缀
- 共享函数库:当一个可执行程序在启动的时候被加载的函数。命名必须以“lib”作为前缀,然后是函数库的名字,然后是“.so”,最后是版本号信息。
1. gcc
- gcc 是 GNU 的 C 和 C++ 编译器
2.Linux系统中可执行文件的两种格式
- a.out格式
- ELF格式:很容易实现共享库。ELF格式已经被Linux系统作为标准的格式采用
gcc编译程序产生的所有的二进制文件都是ELF格式的文件(即使可执行文件的默认名仍然是a.out)
3. GNU C 的使用
3.1 基本语法
gcc [options] [filenames]
3.2 选项
当不用任何选项编译一个程序时,GCC将会建立(假定编译成功)一个名为a.out的可执行文件。
- gcc选项
-o: 用 -o 编译选项来为将产生的可执行文件指定一个文件名来代替 a.out。
例:gcc –o count count.c
-c: 告诉GCC仅把源代码编译为目标代码而跳过汇编和连接的步骤。这个选项使用的非常频繁,因为它使得编译多个C程序时速度更快并且更易于管理。缺省时GCC建立的目标代码文件有一个.o的扩展名。
例:gcc –c test2.c
-E : 只运行 C 预编译器。
-S : 编译选项告诉 gcc 在为 C 代码产生了汇编语言文件后停止编译。
-shared: 生成共享目标文件。通常用在建立共享库时。
-static: 禁止使用共享连接。
- 警告选项
在gcc中用开关-Wall控制警告信息
例:gcc –Wall -o test3_1 test3_1.c
-w 不生成任何警告信息。
- 查找选项
-gcc一般使用默认路径查找头文件和库文件。如果文件所用的头文件或库文件不在缺省目录下,则编译时要指定它们的查找路径。
-I选项:指定头文件的搜索目录
例:gcc –I/export/home/st –o test1 test1.c
-L选项:指定库文件的搜索目录
例:gcc –L/usr/X11/R6/lib –o test1 test1.c
- 优化选项
优化选项可以使GCC在耗费更多编译时间和牺牲易调试性的基础上产生更小更快的可执行文件。这些选项中最典型的是-O和-O2选项。
-O0: 不进行优化处理。
-O: 告诉GCC对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。
-O: 告诉GCC 产生尽可能小和尽可能快的代码。-O2选项将使编译的速度比使用-O时慢。但通常产生的代码执行速度会更快。
-O3: 比 -O2 更进一步优化,包括 inline 函数。
- 版本选项
-v: 用户将会得到自己目前正在使用的gcc的版本及与版本相关的一些信息。
-V : 如果安装了多个版本的gcc,并且想强制执行其中的某个版本,可以用命令通知系统用户要使用的版本。
例:gcc -V7.5.0 -v
- 宏定义选项
-D MACRO : 缺省则以字符串“1”定义 MACRO 宏。
-D MACRO=DEFN: 以字符串“DEFN”定义 MACRO 宏。
-U MACRO: 取消对 MACRO 宏的定义。
- 调试和剖析选项
使用调试选项后,gcc在进行编译的时候,在目标文件(.o)和创建的可执行文件中插入额外信息,这些额外信息使gdb能够判断编译过的代码和源代码之间的关系。
-g: 告诉GCC产生能被 GNU 调试器使用的调试信息以便调试你的程序。
例:gcc –g –o test3 test3.c
-pg:告诉GCC在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示你的程序的耗时情况。
3.3多个源文件生成一个可执行文件
- 方法1:
gcc –Wall –o mytest test1.c test2.c test3.c
- 方法2:
gcc -Wall -c test1.c
gcc -Wall –c test2.c
gcc -Wall –c test3.c
gcc –o mytest test1.o test2.o test3.o
4. GNU 调试工具gdb
- gdb程序调试的对象是可执行文件,而不是程序的源代码文件。
- 如果要让产生的可执行文件可以用来调试,需在执行gcc指令编译程序时,加上-g参数,指定程序在编译时包含调试信息。
4.1 基本语法
gdb [filename]
4.2 gdb命令分类
file命令:装入想要调试的可执行文件。
命令格式:file 文件名
cd命令:改变工作目录。
命令格式:cd 目录名
pwd命令:返回当前工作目录。
list命令:列出正在调试的应用程序的源代码。
命令格式1:List,输出从上次调用list命令开始往后的10行程序代码。
命令格式2:list -,输出从上次调用list命令开始往前的10行程序代码。
命令格式3:list n,输出第n行附近的10行程序代码。
命令格式4:list function,输出函数function前后的10行程序代码。
run命令:执行当前被调试的程序。如果程序有参数,可随后输入。
命令格式: run
kill命令:停止正在调试的应用程序。
命令格式: kill
break命令:设置断点。程序已被载入,并且当前没有运行。
命令格式1:break 行号,当前源文件的某一行上设置断点。
命令格式2:break 函数名,在函数入口设置断点。
命令格式3:break 文件名:行号,在另一文件的某一行上设置断点。
disable :让指定断点失效。使用格式:
命令格式1: disable 断点号列表
watch命令:设置监视点,监视表达式的变化,当表达式的值发生改变时会停止运行。
命令格式:watch 变量或表达式
awatch命令:设置读写监视点。当要监视的表达式被读或写时将应用程序挂起。它的语法与watch命令相同。
rwatch命令:设置读监视点,当监视表达式被读时将程序挂起,等侍调试。此命令的语法与watch相同。
next命令:执行下一条源代码,但是不进入函数内部。执行这个命令的前提是已经run,开始了代码的执行。
命令格式:next
step命令:执行下一条源代码,进入函数内部。执行这个命令的前提是已经用run开始执行代码。
命令格式:step
display命令:在应用程序每次停止运行时显示表达式的值。
命令格式: display 表达式
info break命令:显示当前断点列表,包括每个断点到达的次数
命令格式: info break
info files命令:显示调试文件的信息。
命令格式: info files
info func命令:显示所有的函数名。
命令格式: info func
info local命令:显示当前函数的所有局部变量的信息。
命令格式: info local
info prog命令:显示调试程序的执行状态。
命令格式: info prog
set :设置程序中变量的值。
命令格式: set 变量=表达式
where:查看调用堆栈信息。
命令格式: where
print命令;打印变量或表达式的值。
命令格式:print 变量或表达式
delete命令:删除断点。指定一个断点号码,则删除指定断点。不指定参数则删除所有的断点。
命令格式: delete 断点号
shell命令:执行Linux Shell命令。
命令格式: shell 命令
make命令:不退出gdb而重新编译生成可执行文件。
命令格式:make
quit命令:退出gdb。
命令格式:quit
5. Makefile
规则主要是描述哪些文件(称为target目标文件,不要和编译时产生的目标文件相混淆)是从哪些别的文件(称为dependency依赖文件)中产生的,以及用什么命令(command)来执行这个过程。
5.1 Makefile规则的一般形式
target:dependency dependency
(tab)<command>
一个目标(target),即make最终需要创建的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。
一个或多个依赖文件(dependency)列表,通常是编译目标文件所需要的其他文件。
一系列命今(command),是make执行的动作,通常是把指定的相关文件编译成目标文件的编译命令,每个命令占一行,且每个命令行的起始字符必须为TAB字符。
#例
test:prog.o code.o
gcc –o test prog.o code.o
prog.o:prog.c prog.h code.h
gcc –c prog.c –o prog.o
code.o:code.c code.h
gcc –c code.c –o code.o
clean:
rm –f *.o
在Makefile中,可使用续行号(\)将一个单独的命令行延续成几行。但要注意在续行号(\)后面不能跟任何字符(包括空格和键)
- 调用make命令可输入:
# make target
- target是Makefile文件中定义的目标之一,如果省略target,make就将生成Makefile文件中定义的第一个目标。
- 对于上面Makefile的例子,单独的一个
make
命令等价于:# make test
因为test是Makefile文件中定义的第一个目标,make首先将其读入,然后从第一行开始执行,把第一个目标test作为它的最终目标,所有后面的目标的更新都会影响到test的更新。- 第一条规则说明只要文件test的时间戳比文件prog.o或code.o中的任何一个旧,下一行的编译命令将会被执行。
- 但是,在检查文件prog.o和code.o的时间戳之前,make会在下面的行中寻找以prog.o和code.o为目标的规则,在第三行中找到了关于prog.o的规则,该文件的依赖文件是prog.c、prog.h和code.h。同样,make会在后面的规则行中继续查找这些依赖文件的规则,如果找不到,则开始检查这些依赖文件的时间戳,如果这些文件中任何一个的时间戳比prog.o的新,make将执行“gcc
–c prog.c –o prog.o”命令,更新prog.o文件。- 以同样的方法,接下来对文件code.o做类似的检查,依赖文件是code.c和code.h。当make执行完所有这些套嵌的规则后,make将处理最顶层的test规则。如果关于prog.o和code.o的两个规则中的任何一个被执行,至少其中一个.o目标文件就会比test新,那么就要执行test规则中的命令,因此make去执行gcc命令将prog.o和code.o连接成目标文件test。
- 在上面Makefile的例子中,还定义了一个目标clean,它是Makefile中常用的一种专用目标,即删除所有的目标模块
5.2 make的工作过程
- 首先make按顺序读取makefile中的规则,
- 然后检查该规则中的依赖文件与目标文件的时间戳哪个更新
- 如果目标文件的时问戳比依赖文件还早,就按规则中定义的命令更新目标文件。
- 如果该规则中的依赖文件又是其他规则中的目标文件,那么依照规则链不断执行这个过程,直到Makefile文件的结束,至少可以找到一个不是规则生成的最终依赖文件,获得此文件的时间戳
- 然后从下到上依照规则链执行目标文件的时间戳比此文件时间戳旧的规则,直到最顶层的规则.
- 通过以上的分析过程,可以看到make的优点,因为.o目标文件依赖.c源文件,源码文件里一个简单改变都会造成那个文件被重新编译,并根据规则链依次由下到上执行编译过程,直到最终的可执行文件被重新连接。
- 例如,当改变一个头文件的时候,由于所有的依赖关系都在Makefile里,因此不再需要记住依赖此头文件的所有源码文件,make可以自动的重新编译所有那些因依赖这个头文件而改变了的源码文件,如果需要,再进行重新连接