转自:http://blog.chinaunix.net/space.php?uid=20788470&do=blog&id=1841546
一直觉得自己的shell脚本写的还可以, ms是有点自恋了, 最近在阅读一些高手写的脚本的时候, 却发现有点异常的吃力。 不禁忍不住问自己, 会几个条件判断, 几个循环就是shell编程的高手么? 于是乎再次来到CU shell版, 这里确实有很多很基础的知识, 都是自己不了解的, 有必要沉下心来深入学习/复习一下这些最基本的东西:
当你在命令提示符后键入一个命令/命令列后, shell要做的工作是:
1. 语法分析命令列
2. 处理通配字符(wildcards)、重定向(redirection)、管道(pipes)和作业控制(job control)
3. 搜寻并执行命令
用户登陆后,用户命令同计算机交互的关系为:命令进程--->Shell程序--->UNIX内核--->计算机硬件
几个生僻的符号:
cmd &> file # &> 表示把cmd的stdout和stderr都写入到file
cmd > file 2>&1 # 2>&1 基本同上, 记得2>&1之间不能有空格
n<>file # 打开file用来读写, 并且分配文件描述符n给这个文件。 如果file不存在, 会自动创建
=~ # 正则表达式匹配, 这个必须是bash3.0以后的版本, 且必须用在双中括号中 [[]], 如[[ "$IP" =~ "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$" ]] && echo "ok" || echo "error"
==================================管道与sub-shell===================================
对bash来说, 在管道中的一个大括号中的代码段(块)是运行在一个 子shell中的.
# ls | { read firstline; read secondline; }
# echo "First line is $firstline; second line is $secondline" # 这里看到的是空白
管道两边的代码”块“, 不仅仅只包括“大括号”括起来的块, 也包括while, for等循环中的”块“, 这就是为什么, 经常有人问起,为什么在while循环内赋值的变量, 在循环外面却没见生效, 并不是循环本身的原因, 而是因为这个循环在管道的两边哈
作为子进程的运行的管道, 不能够改变脚本的变量.
1 variable="initial_value"
2 echo "new_value" | read variable
3 echo "variable = $variable" # variable = initial_value
这里可以用read variable <<< "new_value"来替换
LANG=C & 或者export LANG=C &这样也不对当前shell产生影响,改不了LANG这个环境变量
如果管道中的某个命令产生了一个异常,并中途失败,那么这个管道将过早的终止. 这种行为被叫做broken pipe, 并且这种状态下将发送一个SIGPIPE 信号.
bash中产生sub-shell的几种情况
1. cmd | { cmd_list1; ... cmd_listn; }
{ cmdlist; } | { cmd list; }
注意这两种格式, 进程间的关系不一样, 试下{ sleep 10; } | { sleep 20; } 与 sleep 10 | { sleep 20; } 看下进程关系的差别
2. function func() { }
func &
3. {} &
4. { func & } & 这样将产生2个sub-shell
5. (cmd1; cmd2) # 这里的cmd必须大于1个(即cmd group)才会是subshell
6. su user
有些时候需要规避sub-shell:
1. 把echo "" | while结构变成 while < file.txt结构
2. 如果需要对file.txt做一些格式化输出, 可以 while << EOF `cat file.txt` EOF类似于这样的方法来规避
==================================管道与sub-shell===================================
对于位置参数, 大于等于10的位置参数就必须用大括号括起来, 比如echo ${10}。 要访问命令行最后一个参数, 可以${!#}, 或者i=$#, ${!i}, ${!XX}表示间接引用
======================================代码块========================================
{} 使用花括号执行一系列的命令的时候, 如果没有换行, 记得每个领命后面都必须加上分号; 包括最后一个命令, 否则就会报错, 如:
$ { echo "xyz" } # bash会认为的你输入还没有结束
$ vi test.sh
{ echo "xyz" }
$ ./test.sh
./test.sh: line 6: syntax error: unexpected end of file。 如果命令都用换行分开, 最后一个命令与}之间也换行, 就不需要分号
======================================代码块========================================
==============================here document========================================
1. 注意: 结束的limit string, 就是here document最后一行的limit string, 必须开始于第一个字符位置. 它的前面不能够有任何前置的空白. 而在这个limit string后边的空白也会引起异常问题. 空白将会阻止limit string的识别.
如, 有时候你为了对齐, 排版好看一些:
function test()
{
cat << EOF
hello, world
EOF
return 0
}
test
exit 0
执行这个脚本时候, 会报错: " syntax error: unexpected end of file ”, 这里必须把EOF放在行首才能避免这个错误
2. - 选项用来标记here document的limit string (<<-LimitString), 可以抑制输出时前边的tab
(不是空格). 这可以增加一个脚本的可读性.
3. here document 支持参数和命令替换. 所以也可以给here document的消息体传递不同的参数,
这样相应的也会修改输出. 当"limit string"被引用或转义那么就禁用了参数替换, 如
cat << 'EOF'
cat << "EOF"
cat << \EOF 都具有相同的效果, 此时here document里面的引用, 变量替换等都不会做相应的替换, 不过记得用来标识结束的 EOF 不需要加上这些转义之类的符号
4. 注释代码块, 比每行都加上#要好用多了
: << CommentHere
echo "hello, world"
# echo "hello"
CommentHere
==============================here document========================================
在命令行上,把感叹号"!"放在双引号里执行命令会出错(译者注:比如说:echo "hello!"). 因为感叹号被解释成了一个历史命令. 然而在一个脚本文件里,这么写则是正确的,因为在脚本文件里Bash的历史机制被禁用了。
bash的一些新特性:
[[ "abc" > "aba" ]] && echo true # [[]] 不仅可以比较数值的大小, 还可以比较字符串大小
++++++++++字符串处理
我们也可以对变量值里的字符串作替换:
${file/dir/path}:将第一个dir替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部dir替换为path:/path1/path2/path3/my.file.txt
${kk//[!0-9]/ }
========================bash里的冒号=================================
冒号:
空命令, 是bash的内建命令, 什么也不干的命令, 相当与true, 返回0
一般用法:
一. 死循环 while : do oper_lists; done
二. 在if/then中提供占位符
if condition
then : # 引发一个分支
else
oper_lists
fi
三. : ${variable="xxx"} # 在一个二元命令中提供一个占位符
: ${variable:="xxx"} # ${xxx:=} 对不同状态的变量赋值的时候, 一般要和: 结合使用
四. 跟here document结合注释bash代码块
: << EOF
echo "hello, world"
......
EOF
这个有点类似于c/cpp里的利用编译预处理 #if 0 .... #endif来注释一段大的代码块
========================bash里的冒号=================================
ps 的时候看到的bash为什么之前有一个-呢?
-bash
这个表示这是一个登录shell
如果只是 bash, 表示一个非登录shell, 用su xxx试试, 然后ps一下
===========================函 数===========================
1. 在函数中, 声明的变量, 如果没有特别指明是local的, 在函数体外也可以用
2. 在函数中调用另外一个函数, 被调用的函数可以在调用的函数之后再定义/声明(bash中没有函数声明的概念)。 比如:
function a()
{
echo "i'm a"
b
}
function b()
{
echo "i'm b"
}
3. 但是如果有个公共函数库, 你必须先调用公共库, 才能执行公共库中的函数, 比如:
. ./functions
func_in_functions
而不能
func_in_functions
. ./functions
===========================函 数===========================