一、在循环中使用管道的技巧:
在Bash Shell中,管道的最后一个命令都是在子Shell中执行的。这意味着在子Shell中赋值的变量对父Shell是无效的。所以当我们将管道输出传送到一个循环结构,填入随后将要使用的变量,那么就会产生很多问题。一旦循环完成,其所依赖的变量就不存在了。
/> cat > test8_1.sh
#!/bin/sh
#1. 先将ls -l命令的结果通过管道传给grep命令作为管道输入。
#2. grep命令过滤掉包含total的行,之后再通过管道将数据传给while循环。
#3. while read line命令从grep的输出中读取数据。注意,while是管道的最后一个命令,将在子Shell中运行。
ls -l | grep -v total | while read line
do
#4. all变量是在while块内声明并赋值的。
all="$all $line"
echo $line
done
#5. 由于上面的all变量在while内声明并初始化,而while内的命令都是在子Shell中运行,包括all变量的赋值,因此该变量的值将不会传递到while块外,因为块外地命令是它的父Shell中执行。
echo "all = " $all
/> ./test8_1.sh
-rw-r--r--. 1 root root 193 Nov 24 11:25 outfile
-rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
-rwxr-xr-x. 1 root root 108 Nov 24 12:48 test8_1.sh
all =
为了解决该问题,我们可以将while之前的命令结果先输出到一个临时文件,之后再将该临时文件作为while的重定向输入,这样while内部和外部的命令都将在同一个Shell内完成。
/> cat > test8_2.sh
#!/bin/sh
#1. 这里我们已经将命令的结果重定向到一个临时文件中。
ls -l | grep -v total > outfile
while read line
do
#2. all变量是在while块内声明并赋值的。
all="$all $line"
echo $line
#3. 通过重定向输入的方式,将临时文件中的内容传递给while循环。
done
#4. 删除该临时文件。
rm -f outfile
#5. 在while块内声明和赋值的all变量,其值在循环外部仍然有效。
echo "all = " $all
/> ./test8_2.sh
-rw-r--r--. 1 root root 0 Nov 24 12:58 outfile
-rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
-rwxr-xr-x. 1 root root 140 Nov 24 12:58 test8_2.sh
all = -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_2.sh
上面的方法只是解决了该问题,然而却带来了一些新问题,比如临时文件的产生容易导致性能问题,以及在脚本异常退出时未能及时删除当前使用的临时文件,从而导致生成过多的垃圾文件等。下面将再介绍一种方法,该方法将同时解决以上两种方法同时存在的问题。该方法是通过HERE-Document的方式来替代之前的临时文件方法。
/> cat > test8_3.sh
#!/bin/sh
#1. 将命令的结果传给一个变量
OUTFILE=`ls -l | grep -v total`
while read line
do
all="$all $line"
echo $line
done <
#2. 将该变量作为该循环的HERE文档输入。
$OUTFILE
EOF
#3. 在循环外部输出循环内声明并初始化的变量all的值。
echo "all = " $all
/> ./test8_3.sh
-rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
-rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
all = -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
二、自链接脚本:
通常而言,我们是通过脚本的命令行选项来确定脚本的不同行为,告诉它该如何操作。这里我们将介绍另外一种方式来完成类似的功能,即通过脚本的软连接名来帮助脚本决定其行为。
文件名:test9.sh
#!/bin/sh
#basename命令将剥离脚本的目录信息,只保留脚本名,从而确保在相对路径的模式下执行也没有任何差异。
#通过sed命令过滤掉脚本的扩展名。
dowhat=`basename $0 | sed 's/\.sh//'`
echo $dowhat
#这里的case语句只是为了演示方便,因此模拟了应用场景,在实际应用中,可以为不同的分支执行不同的操作,或将某些变量初始化为不同的值和状态。
case $dowhat in
test9)
echo "I am test9.sh"
;;
test9_1)
echo "I am test9_1.sh."
;;
test9_2)
echo "I am test9_2.sh."
;;
*)
echo "You are illegal link file."
;;
esac
[root@wu 桌面]# ln -s test9.sh test9_2.sh
[root@wu 桌面]# ./test9_2.sh
test9_2
I am test9_2.sh.
三、编写一个更具可读性的df命令输出脚本
#!/bin/bash
#1. $$表示当前Shell进程的pid。
#2. trap信号捕捉是为了保证在Shell正常或异常退出时,仍然能够将该脚本创建的临时awk脚本文件删除。
awk_script_file="/tmp/scf_tmp.$$"
trap "rm -f $awk_script_file" EXIT
#3. 首先需要说明的是,'EOF'中的单引号非常重要,如果忽略他将无法通过编译,这是因为awk的命令动作必须要用单引号扩住。
#4. awk脚本的show函数中,int(mb * 100) / 100这个技巧是为了保证输出时保留小数点后两位。
cat < $awk_script_file
function show(size) {
mb = size / 1024;
int_mb = (int(mb * 100)) / 100;
gb = mb / 1024;
int_gb = (int(gb * 100)) / 100;
if (substr(size,1,1) !~ "[0-9]" || substr(size,2,1) !~ "[0-9]") {
return size;
} else if (mb
return size "K";
} else if (gb
return int_mb "M";
} else {
return int_gb "G";
}
}
#5. 在BEGIN块中打印重定义的输出头信息。
BEGIN {
printf "%-20s %7s %7s %7s %8s %s\n","FileSystem","Size","Used","Avail","Use%","Mounted"
}
#6. !/文件系统/ 表示过滤掉包含Filesystem的行,即df输出的第一行。其余行中,有个域字段可以直接使用df的输出,有的需要通过show函数的计算,以得到更为可读的显示结果。
!/文件系统/ {
size = show($2);
used = show($3);
avail = show($4);
printf "%-20s %7s %7s %7s %8s %s\n",$1,size,used,avail,$5,$6
}
EOF
df -k | awk -f $awk_script_file
[wulei@bogon 桌面]$ ./dd
FileSystem Size Used Avail Use% Mounted
/dev/sda2 17.45G 2.47G 14.09G 15% /
tmpfs 503.26M 232K 503.03M 1% /dev/shm
/dev/sda1 290.51M 31.03M 244.47M 12% /boot