Unix Toolbox

 主页   资讯   文章   代码   电子书 

Bourne shell 译注:Shell 存在很多种,如 bash(Bourne Again Shell),csh(C Shell),tcsh(TC Shell),zsh(Z Shell) 等。通过 ps 命令可识别出正在运行的是哪种 Shell。 (/bin/sh) 存在于所有的 Unix 系统上,并且用她写的脚本是(完全)可移植的; man 1 sh 是一个好的参考。

基础

变量和参数

使用 variable=value 的命令格式设置变量,其中 variable 是变量名称,value是打算赋给该变量的值。使用 $variable 获取变量值。

MESSAGE="Hello World"                        # 赋予一个字符串
PI=3.1415                                    # 赋予一个十进制小数
N=8
TWON=`expr $N * 2`                           # 算术表达式(只限整数)
TWON=$(($N * 2))                             # 另一种语法
TWOPI=`echo "$PI * 2" | bc -l`               # 使用 bc 进行浮点运算
ZERO=`echo "c($PI/4)-sqrt(2)/2" | bc -l`

命令行参数:

$0, $1, $2, ...                              # $0 命令本身
$#                                           # 命令参数个数
$*                                           # 所有参数(也可以是 $@)

一些特殊的变量

$$                                           # 当前进程 ID
$?                                           # 最后命令退出状态码
  command
  if [ $? != 0 ]; then
    echo "command failed"
  fi
mypath=`pwd`
mypath=${mypath}/file.txt
echo ${mypath##*/}                           # 只显示文件名
echo ${mypath%%.*}                           # 除了扩展名的全路径
var2=${var:=string}                          # 如果var没有被赋值,则string值先赋值给var,
                                             # 然后再赋值给var2

结构控制

for file in `ls`
do
    echo $file
done

count=0
while [ $count -lt 5 ]; do
    echo $count
    sleep 1
    count=$(($count + 1))
done

myfunction() {
    find . -type f -name "*.$1" -print       # $1 为方法的第一个参数
}
myfunction "txt"

产生一个文件

MYHOME=/home/colin
cat > testhome.sh << _EOF
# 所有_EOF前的代码都会进入到 testhome.sh 文件中去
if [ -d "$MYHOME" ] ; then
    echo $MYHOME exists
else
    echo $MYHOME does not exist
fi
_EOF
sh testhome.sh

Bourne 脚本实例

来一个小实例,此脚本从本 xhtml 文档创建一个 PDF 小册子:

#!/bin/sh
# 此脚本可以创建一份供双面打印机打印的 PDF 格式的书
if [ $# -ne 1 ]; then                        # 检查参数是否等于 1
  echo 1>&2 "Usage: $0 HtmlFile"
  exit 1                                     # 如果不等于1,非0退出
fi

file=$1                                      # 文件变量
fname=${file%.*}                             # 文件名变量
fext=${file#*.}                              # 文件扩展名变量

prince $file -o $fname.pdf                   # www.princexml.com
pdftops -paper A4 -noshrink $fname.pdf $fname.ps # 创建 postscript 小册子
cat $fname.ps |psbook|psnup -Pa4 -2 |pstops -b "2:0,1U(21cm,29.7cm)" > $fname.book.ps

ps2pdf13 -sPAPERSIZE=a4 -sAutoRotatePages=None $fname.book.ps $fname.book.pdf
                                             # 在 Windows 上使用 #a4 和 #None!
exit 0                                       # exit 0 意为成功

一些 sed 命令

这里是单行 sed 命令的金矿http://student.northpark.edu/pemente/sed/sed1line.txt 。还有一个很好的 sed 介绍及教程http://www.grymoire.com/Unix/Sed.html 。

sed 's/string1/string2/g'                    # 替换 string1 为 string2
sed -i 's/wroong/wrong/g' *.txt              # 用 g 替换所有返回的单词
sed 's/\(.*\)1/\12/g'                        # 修改 anystring1 为 anystring2
sed '/<p>/,/<\/p>/d' t.xhtml                 # 删除以 <p> 开始,以 </p> 结尾的行
sed '/ *#/d; /^ *$/d'                        # 删除注释和空行
sed 's/[ \t]*$//'                            # 删除行尾空格 (使用 tab 代替 \t)
sed 's/^[ \t]*//;s/[ \t]*$//'                # 删除行头尾空格
sed 's/[^*]/[&]/'                            # 括住首字符 [] top -> [t]op
sed = file | sed 'N;s/\n/\t/' > file.num     # 为文件添加行号

正则表达式

一些基本的正则表达式同样可用于 sed。作为一个良好的启蒙,可看 基本正则语法http://www.regular-expressions.info/reference.html 。

[\^$.|?*+()                          # 特殊字符,其他字符将匹配自身
\                                    # 转义特殊字符,当成普通字符对待
*                                    # 重复前项 0 次或多次
.                                    # 单个字符除换行符
.*                                   # 匹配 0 个或多个字符
^                                    # 匹配字符串行开始处
$                                    # 匹配字符串行结尾处
.$                                   # 匹配字符串行最后一个字符
^ $                                  # 匹配单个空格的行
[^A-Z]                               # 匹配任何以 A-Z 字符开始的行

一些实用命令

下列命令对于包含于一个脚本或者单行命令来说很有用。

sort -t. -k1,1n -k2,2n -k3,3n -k4,4n         # 排序 IPv4 格式的 IP 地址
echo 'Test' | tr '[:lower:]' '[:upper:]'     # 转换成大写
echo foo.bar | cut -d . -f 1                 # 返回 foo
PID=$(ps | grep script.sh | grep bin | awk '{print $1}')          # 正在运行名为 script 脚本的 PID
PID=$(ps axww | grep [p]ing | awk '{print $1}')                   # ping 的 PID (w/o grep pid)
IP=$(ifconfig $INTERFACE | sed '/.*inet addr:/!d;s///;s/ .*//')   # Linux
IP=$(ifconfig $INTERFACE | sed '/.*inet /!d;s///;s/ .*//')        # FreeBSD
if [ `diff file1 file2 | wc -l` != 0 ]; then [...] fi             # 文件改变了?
cat /etc/master.passwd | grep -v root | grep -v \*: | awk -F":" \ # 创建 http passwd
'{ printf("%s:%s\n", $1, $2) }' > /usr/local/etc/apache2/passwd

testuser=$(cat /usr/local/etc/apache2/passwd | grep -v \    # 查看 passwd 中的用户
root | grep -v \*: | awk -F":" '{ printf("%s\n", $1) }' | grep ^user$)
:(){ :|:& };:                                # bash fork 炸弹。会干掉你的机器译注:http://forum.ubuntu.org.cn/viewtopic.php?t=92074
tail +2 file > file2                         # 删除文件的第一行

我使用一种小伎俩来一次更改许多文件的扩展名。举个例子,从 .cxx 到 .cpp。排除最后的 | sh 先测试一下。你同样可以使用命令 rename 来做这些,如果安装了的话。或者使用 bash 内建命令。

# ls *.cxx | awk -F. '{print "mv "$0" "$1".cpp"}' | sh
# ls *.c | sed "s/.*/cp & &.$(date "+%Y%m%d")/" | sh # 如 拷贝 *.c 成 *.c.20080401
# rename .cxx .cpp *.cxx                             # 重命名所有 .cxx 成 .cpp
# for i in *.cxx; do mv $i ${i%%.cxx}.cpp; done      # bash 内建的