一些让人很容易忽视的shell基础知识

转自: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
===========================函   数===========================
 

你可能感兴趣的:(shell,function,String,cmd,脚本,bash)