shell基础

管道和重定向

  • 重定向 下面的命令演示了STDOUT和STDERR分开处理的原因:
$ find / -name core
这样会导致很多‘permission denied’这样的信息;
$ find / -name core 2> /dev/null
把错误重定向到/dev/null
$ find / -name core > /tmp/corefiles 2> /dev/null
把匹配的路径重定向到一个文件;
  • 管道
$ ps -ef | grep httpd
把httpd进程打印出来;
$ cut -d: -f7 < /etc/passwd | sort -u
把每个用户的shell路径选出来并排序。

在一个脚本里,可以用反斜线把一条命令分成多行来写,如果要实现相反的效果,将多条命令整合在一行,可以用分号作为语句分隔符。

变量和引用

$ mylang="Pennsylvania Dutch"
$ echo "I speak ${mylang}"
I speak Pennsylvania Dutch
$ echo 'I speak ${mylang}'
I speak ${mylang}

bash脚本

下面是一个有用的例子:

$ find . -type f -name '*.log' 2> /dev/null
./leather.log
./foo.log
./genius/spew.log
./.do-not-touch/important.log

需要把‘’.do-not-touch目录排除:
$ find . -type f -name '*.log' 2> /dev/null  | grep -v .do-not-touch  
./leather.log
./foo.log
./genius/spew.log

生成一些新的名字:
$ find . -type f -name '*.log' 2> /dev/null  | grep -v .do-not-touch  | while read fname
> do
> echo mv $fname ${fname/.log/.LOG}
> done
mv ./leather.log ./leather.LOG
mv ./foo.log ./foo.LOG
mv ./genius/spew.log ./genius/spew.LOG

生成的这几条命令就是我们需要的,当我们输入<ctrl-p>的时候,发现bash已经把上面的命令变成了一行:
$ find . -type f -name '*.log' 2> /dev/null | grep -v .do-not-touch  | while read fname; do echo mv $fname ${fname/.log/.LOG}; done | bash -x
加上'bash -x',执行每条命令之前都会打印这条命令。最后用fc命令把送到用户的默认文本剪辑器,加上必要的#!和说明。
> 修改默认文本编辑器:echo export EDITOR=/usr/bin/vim >> ~/.bashrc

总结步骤:

  1. 按一个管道的方式开发脚本,完全在命令行上做;

  2. 把输出送到标准输出,检查并确保结果正确;

  3. 每一步,用重新找回命令;

  4. 用fc命令捕获并修改命令;

输入和输出

如果要对输出做更多的控制,需要使用printf命令:

$ printf  "\taa\tbb\tcc"
    aa  bb  cc

用read命令可以提示输入:

#!/bin/bash

echo -n "Enter your name: "
read user_name

if [ -n "$user_name" ]; then
    echo "Hello, $user_name!"
    exit 0
else
    echo "You did not tell you name!"
    exit 1
fi

echo命令的-n选项消除了通常的换行符。if的-n判断其字符串参数是否为空。

命令行参数与函数

  • $1: 第一个参数
  • $0: 该脚本所用的名字
  • $#: 参数的个数
  • $*: 保存有全部的参数
#!/bin/bash

function show_usage {
    echo "Usage: $0 source_dir dest_dir"
    exit 1
}

# Main program starts here

if [ $# -ne 2 ]; then
    show_usage
else
    if [ -d $1 ]; then
        source_dir=$1
    else
        echo "Invalid source directory"
        show_usage
    fi
    if [ -d $2 ]; then
        dest_dir=$2
    else
        echo "Invalid dest directory"
        show_usage
    fi
fi

printf "Source directory is ${source_dir}\n"
printf "Destination directory is ${dest_dir}\n"

可以改进show_usage函数:

function show_usage {
    echo "Usage: $0 source_dir dest_dir"
    if [ $# -eq 0 ]; then
        exit 99
    else
        exit $1
}

可以给一个确定的出错码值:
show_usage 5

在bash里,函数和命令之间很相似,用户可以在自己的~/.bash_profile文件里面定义自己的函数,然后在命令行上使用它们:

function ssh {
    /usr/bin/ssh -p 7988 $*
}

变量的作用域

在脚本里的变量是全局变量,但是函数可以用local声明语句,创建自己的局部变量。

控制流程

if [ $base -eq 1 ] && [ $dm -eq 1 ]; then
    installDMBase
elif [ $base -ne 1 ] && [ $dm -eq 1 ]; then
    installBase
elif [ $base -eq 1 ] && [ $dm -ne 1 ]; then
    installDM
else
    echo '==> Install noting'
fi

循环

可以对*或者?这样的模式匹配符进行扩展:

#!/bin/bsh

suffix=BACKUP--`date +%Y%m%d-%H%M`

for script in *.sh; do
    newname="$script.$suffix"
    echo "Copying $script to $newname..."
    cp $script $newname
done

bash也有标准的for循环:

for (( i=0; i < $CPU_COUNT; i++ )); do
    CPU_LIST="$CPU_LIST $i"
done

下面是bash的while循环:

#!/bin/bash

exec 0<$1
counter=1
while read line; do
    echo "$counter: $line"
    $((counter++))
done

下面是输出结果:

ubuntu$ sh whileexample /etc/passwd

这里的exec语句重新定义了脚本的标准输入,变成由第一个命令参数指定的文件。