月度归档:2013年10月

Linux程序设计 – Shell程序设计

以下是“Linux程序设计”关于Shell程序设计的笔记,省略了一些内容。对于Shell的探讨趋向于简单罗列并且很粗糙,不是入门的材料。还是鸟哥的材料比较好,可以参考以下笔记:
Linux系统管理 – 使用vi/vim
Linux系统管理 – Bash Shell基础
Linux系统管理 – 正则表达式与文件格式化
Linux系统管理 – Shell Script

###管道和重定向

1 重定向输出

>
>>
2>
2>&1

2 从定向收入
3 管道

###作为程序设计语言的shell

1 交互式程序
2 创建脚本
脚本程序本质上被看作是shell的标准输入,所以他可以包含任何能够通过你的PATH环境变量引用到的Linux命令。

exit命令的作用是确保脚本程序能够返回一个有意义的退出码。

在shell程序设计里,0表示成功。因为这个脚本程序并不能检查到任何错误,所以它总是返回一个表示成功的退出码。

3 把脚本设置为可以执行

###Shell的语法

1 变量
直接赋值,全部看待成字符串,引用变量时需要使用美元符,区分大小写,特别,如果字符串包含空格,就必须用引号把它们括起来,等号两边不能有空格。

1) 使用引号
字符串通常都放在双引号中,以防止变量被空白字符分开,同时又运行使用美元符。使用单引号括起来的字符串,美元符的引用将不起作用。

2) 环境变量

$HOME	当前用户的家目录
$PATH	以冒号分割的用来搜索命令的目录列表
$PS1	命令提示符,通常是$字符...
$PS2	二级提示符,通常是>
$IFS	
$0	Shell脚本的名字
$#	传递给脚本的参数个数
$$	Shell脚本的进程号,脚本程序通常用它来生成一个唯一的临时文件,如/tmp/tmpfile_$$

3) 参数变量

$1, $2, ...	脚本程序的参数
$*		在一个变量中列出所有的参数,各个参数之间用环境变量IFS中的第一个字符分隔开。
$@		是$*的一种精巧变体 固定是用空格分隔

2 条件
一个shell脚本能够对任何可以从命令行上调用的命令的退出码进行测试。

test或[命令
当使用[时,还使用符合]来结束。如果把then和if放在同一行上,就必须要用一个分号把test语句和then分隔开:

if [ -f t.sh ]; then
...
fi

if [ -f t.sh ]
then
...
fi

if test -f t.sh; then
...
fi

if test -f t.sh
then 
...
fi




##字符串比较
string1 = string2   	###注意跟赋值的却别,赋值时等号两边不能有空格
string1 != string2
-n string		字符串是否为空,为空结果为真		
-z string		字符串是否为null(一个空串),为null则结果为真

##算术比较
exp1 -eq exp2
exp1 -ne exp2
exp1 -gt exp2
exp1 -ge exp2
exp1 -lt exp2
exp1 -le exp2
! exp

##文件条件测试
-d file			目录
-e file			目录或文件
-f file			文件
-g file			SGID
-r file			可读
-s file			文件大小不为0
-u file			SUID位
-w file			可写
-x file			可执行

3 控制结构
1)if语句
2)elif语句
3)一个与变量有关的问题
考虑如下代码:

if [ $timeofday = "yes" ]

当timeofday变量没有设置时将得到

if [  = "yes" ]

这里会提示错误。所以为了避免这种情况,必须给变量加上引号。即使变量没有设置,也会得到:

if [ "" = "yes" ]

4) for语句

##把指直接在in后列出来
for foo in bar fud 43
do
    echo $foo
done

##使用通配符扩展for循环
for file in $(ls f*.sh); do
  lpr $file
done

5) while语句

read trythis
while [ "$trythis" != "secret" ]; do
 echo "try again"
 read trythis
done

6) until语句
7)case语句

read timeofday
case "$timeofday" in
  yes) echo "Good Morning";;
  y  ) echo "Good Morning"
  no ) echo "Good Afternoon";;
  *  ) echo "Sorry.";;
esac

##合并匹配模式
read timeofday
case "$timeofday" in
  yes | y | Yes | YES) echo "Good Morning";;
  no ) echo "Good Afternoon";;
  *  ) echo "Sorry.";;
esac

##执行多条语句
##合并匹配模式
read timeofday
case "$timeofday" in
  yes | y | Yes | YES) 
	echo "Good Morning"
	echo "Up bright and early this moring"
	;;
  no ) echo "Good Afternoon";;
  *  ) echo "Sorry.";;
esac

为了让case匹配功能更强大,可以使用如下模式:

[yY] | [Yy][Ee][Ss]

注意,如果*之后没有其它的匹配,那么它之后的;;是可以省略的。

8)命令列表(&& 和 ||)
9)语句块(使用{}括起来)

4 函数
当一个函数被调用时,脚本程序的位置参数($* $@ $# $1 $2等)会被替换为函数的参数。这也是读取传递给函数的参数的办法。当函数执行完毕后,这些参数会恢复它们先前的值。

可以通过return命令让函数返回数字值。或者可以把结果保存到一个变量中。或者用echo输出,然后捕获输出:

foo () { echo vfeelit; }
...
result="$(foo)"

如下程序描述函数参数如何传递(需要好好体会)

#!/bin/sh

yes_or_no() {
  echo "Is your name $* ?"  ##把传递进来的参数全部输出  $*不再是脚本传递进来的值,是当前函数里的被改变的值
  while true
  do
      echo -n "Enter yes or no: "
      read x
      case "$x"	in
  	y | yes ) return 0;;
	n | no ) return 1;;
	* ) echo "Answer yes or no"
      esac
  done
}

echo "Original parameters are $*"

if yes_or_no "$1"   ##把脚本的第一个参数传递给函数
then
  echo "Hi $1, nice name"
else
  echo "Never mind"
fi

exit 0

5 命令
1) break
2) :命令
冒号(:)命令是一个空命令。它偶尔会被用于简化条件逻辑,相当于true的一个别名。
3) continue命令
4).命令
执行一个shell脚本将使得脚本在一个子shell中运行,如果希望它就在当前shell里执行,可以使用点命令。
5)echo命令

foo=10
x=foo
y='$'$x
echo $y
#以上直接输出字符串$foo

foo=10
x=foo
eval y='$'$x
echo $y
#以上输出10,y='$'$x被解析为y=$foo这个字符串,这时eval把这个字符串当做脚本解析,意思是y被赋值10,所以最后输入10

7)exec命令
8)exit n命令
exit命令使脚本程序以退出码n结束运行。如果你允许自己的脚本程序在退出时不制定一个退出状态,那么该脚本中最后一条被执行命令的状态将被用作为方回值。
9)export命令
将换一个变量变成环境变量
10) expr命令
expr命令将它的参数当作一个表示式来求值。

exp1 | exp2
exp1 & exp2
exp1 = exp2
exp1 > exp2
exp1 >= exp2
exp1 < exp2
exp1 <= exp2
exp1 != exp2
exp1 + exp2
exp1 - exp2
exp1 * exp2
exp1 / exp2
exp1 % exp2

在较新的脚本程序中,expr命令通常被替换为更加有效的$((…))语法。

11) printf命令
12)return命令
13)set命令
set命令的作用是为shell设置参数变量(映射到$引用中)。许多命令的输出结果是以空格分隔的值,如果需要使用输出结果中的某个域,这个命令非常有用。
14)shift命令
shift命令把所有参数变量左移一个位置,使$2变成$1,$3变成$2,以此类推。原来$1的值将被丢弃,$0将保持不变。如果调用shift命令时指定了一个数值参数,则表示所有的参数将左移制定的次数。

15)trap命令
trap命令用于指定在接收到信号后将要采取的行动。trap命令的一种常见用途是在脚本程序被中断时完成清理工作。
16)unset命令

永久链接:http://blog.ifeeline.com/1009.html

Linux程序设计 – 开发系统导引(C程序编译)

开发系统导引

1 应用程序
2 头文件
用C语言进行程序设计时,需要用头文件来提供对常量的定义和对系统函数及库函数调用的声明。对C语言来说,这些头文件几乎总是位于/usr/include目录及子目录中。

在调用C语言编译器时,可以使用-I标志来包含保存在子目录货非标准位置中的头文件。

用grep命令来搜索包含某些特定定义和函数原型的头文件时很方便的。如想知道用于从程序中放回退出状态的#define定义的名字,只需要切换到/usr/include目录下,然后用grep命令搜索可能的名字:

grep EXIT_ *.h

stdlib.h:#define        EXIT_FAILURE    1       /* Failing exit status.  */
stdlib.h:#define        EXIT_SUCCESS    0       /* Successful exit status.  */

3 库文件
标准系统库文件一般存储在/lib和/usr/lib目录中。C语言编译器(或确切地说是链接程序)需要知道要搜索哪些库文件,因为在默认情况下,它只搜索标准C语言库。

库文件的名字总是以lib开头,随后的部分指明这是什么库(如c代表C语言库,m代表数学库)。文件名的最后部分以.开始,然后给出库文件的类型:

.a	代表传统的静态函数库
.so	代表共享函数库

函数库通常以静态和共享库两种格式存在。

可以通过给出完整的库文件路径或用-l标志告诉编译器要搜索的库文件:

gcc -o fred fred.c /usr/lib/libm.a	#直接给出文件路径

下面的命令也能产生类似结果:

gcc -o fred fred.c -lm			#通过-l指定要使用的库

只要使用ar程序和使用gcc -c命令对函数分别进行编译,就可以创建和维护静态库。例子如下:

vi fred.c
#include <stdio.h>
void fred(int arg)
{
        printf("fred: we passed %d\n",arg);
}

vi bill.c
#include <stdio.h>

void bill(char *arg)
{
        printf("bill: we passed %s\n", arg);
}

##编译成中间文件
gcc -c bill.c fred.c
ls *.o
bill.o  fred.o

##为库文件编写头文件
vi lib.h
void bill(char *);
void fred(int);

##编写一个调用bill函数的程序
vi program.c
#include <stdlib.h>
#include "lib.h"

int main()
{
        bill("Hello World");
        exit(0);
}

##编译program.c
gcc -c program program.c

##链接函数库 生成二进制文件
gcc -o program program.o bill.o  #program只需要用到bill.o

##执行
./program
bill: we passed Hello World

##接下来创建一个库文件
ar crv libfoo.a bill.o free.o

##使用库文件重新链接 生成二进制文件
gcc -o program program.o libfoo.a
./program
bill: we passed Hello World

##也可以使用-l选项来访问函数库,但因其未保存在标准位置,所以必须使用-L选项来告诉编译器在何处找到它
gcc -o program program.o -L. -lfoo

###########
以上-L.选项告诉编译器在当前目录(.)中查找函数库。-lfoo选项告诉编译器使用名为libfoo.a的函数库(或者名为libfoo.so的共享库)。要查看哪些函数被包含在目标文件、函数库或可执行文件里,可以使用nm命令。当程序被创建时,它只包含函数库中它实际需要的函数。虽然程序中的头文件包含函数库中所有函数的声明,但并不会将整个函数库包含在最终的程序中。

5 共享库
共享库的保存位置与静态库是一样的,但共享库有不同的文件名后缀。在一个典型的Linux系统中,标准数学库的共享版本是/usr/lib/libm.so。

当一个程序使用共享库时,它的链接方式是这样的:程序本身不再包含函数代码,而是引用运行时可以访问的共享代码。当编译好的程序被装到内存中执行时,函数引用被解析并产生对共享库的调用,如果有必要,共享库才被加载到内存中。

通过这种方法,系统可以只保留共享库的一份副本供许多应用程序同时使用,并且在磁盘上也仅保存一份。另一个好处是共享库的更新可以独立于依赖它的应用程序。如,文件/lib/bibm.so就是对实际库文件修订版本(/lib/libm/so.N,其中N代表著版本号)的符号链接。

对Linux系统来说,负责装载共享库并解析客户程序函数引用的程序(动态装载器)是ld.so。用于搜索共享库的额外位置可以在文件/etc/ld.so.conf中配置,如果修改了这个文件,需要执行命令ldconfig来处理它。

可以通过运行ldd工具查看一个程序需要的共享库。

永久链接:http://blog.ifeeline.com/1006.html