非直接引用变量:
在Shell中提供了三种为标准(直接)变量赋值的方式:
1. 直接赋值。
2. 存储一个命令的输出。
3. 存储某类型计算的结果。
然而这三种方式都是给已知变量名的变量赋值,如name=Stephen。但是在有些情况下,变量名本身就是动态的,需要依照运行的结果来构造变量名,之后才是为该变量赋值。这种变量被成为动态变量,或非直接变量。
代码如下 | 复制代码 |
/> cat > test7.sh #!/bin/sh work_dir=`pwd` |
#1. 由于变量名中不能存在反斜杠,因此这里需要将其替换为下划线。
#2. work_dir和file_count两个变量的变量值用于构建动态变量的变量名。
代码如下 | 复制代码 |
work_dir=`echo $work_dir | sed 's///_/g'` file_count=`ls | wc -l` |
#3. 输出work_dir和file_count两个变量的值,以便确认这里的输出结果和后面构建的命令名一致。
代码如下 | 复制代码 |
echo "work_dir = " $work_dir echo "file_count = " $file_count |
#4. 通过eval命令进行评估,将变量名展开,如${work_dir}和$file_count,并用其值将其替换,如果不使用eval命令,将不会完成这些展开和替换的操作。最后为动态变量赋值。
eval BASE${work_dir}_$file_count=$(ls $(pwd) | wc -l)
#5. 先将echo命令后面用双引号扩住的部分进行展开和替换,由于是在双引号内,仅完成展开和替换操作即可。
#6. echo命令后面的参数部分,先进行展开和替换,使其成为$BASE_root_test_1动态变量,之后在用该变量的值替换该变量本身作为结果输出。
代码如下 | 复制代码 |
eval echo "BASE${work_dir}_$file_count = " '$BASE'${work_dir}_$file_count CTRL+D /> . ./test7.sh work_dir = _root_test file_count = 1 BASE_root_test_1 = 1 |
八、在循环中使用管道的技巧:
在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 CTRL+D /> ./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 < outfile
#4. 删除该临时文件。
rm -f outfile
#5. 在while块内声明和赋值的all变量,其值在循环外部仍然有效。
代码如下 | 复制代码 |
echo "all = " $all CTRL+D /> ./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 <<EOF |
#2. 将该变量作为该循环的HERE文档输入。
$OUTFILE
EOF
#3. 在循环外部输出循环内声明并初始化的变量all的值。
代码如下 | 复制代码 |
echo "all = " $all CTRL+D /> ./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 |
九、自链接脚本:
通常而言,我们是通过脚本的命令行选项来确定脚本的不同行为,告诉它该如何操作。这里我们将介绍另外一种方式来完成类似的功能,即通过脚本的软连接名来帮助脚本决定其行为。
代码如下 | 复制代码 |
/> cat > test9.sh #!/bin/sh |
#1. basename命令将剥离脚本的目录信息,只保留脚本名,从而确保在相对路径的模式下执行也没有任何差异。
#2. 通过sed命令过滤掉脚本的扩展名。
代码如下 | 复制代码 |
dowhat=`basename $0 | sed 's/.sh//'` |
#3. 这里的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 CTRL+D /> chmod a+x test9.sh /> ln -s test9.sh test9_1.sh /> ln -s test9.sh test9_2.sh /> ls -l lrwxrwxrwx. 1 root root 8 Nov 24 14:32 test9_1.sh -> test9.sh lrwxrwxrwx. 1 root root 8 Nov 24 14:32 test9_2.sh -> test9.sh -rwxr-xr-x. 1 root root 235 Nov 24 14:35 test9.sh /> ./test9.sh I am test9.sh. /> ./test9_1.sh I am test9_1.sh. /> ./test9_2.sh I am test9_2.sh. |
十、Here文档的使用技巧:
在命令行交互模式下,我们通常希望能够直接输入更多的信息,以便当前的命令能够完成一定的自动化任务,特别是对于那些支持自定义脚本的命令来说,我们可以将脚本作为输入的一部分传递给该命令,以使其完成该自动化任务。
#1. 通过sqlplus以dba的身份登录Oracle数据库服务器。
#2. 在通过登录后,立即在sqlplus中执行oracle的脚本CreateMyTables和CreateMyViews。
#3. 最后执行sqlplus的退出命令,退出sqlplus。自动化工作完成。
代码如下 | 复制代码 |
/> sqlplus "/as sysdba" <<-SQL > @CreateMyTables > @CreateMyViews > exit > SQL |
十一、获取进程的运行时长(单位: 分钟):
在进程监控脚本中,我们通常需要根据脚本的参数来确定有哪些性能参数将被收集,当这些性能参数大于最高阈值或小于最低阈值时,监控脚本将根据实际的情况,采取预置的措施,如邮件通知、直接杀死进程等,这里我们给出的例子是收集进程运行时长性能参数。
ps命令的etime值将给出每个进程的运行时长,其格式主要为以下三种:
1. minutes:seconds,如20:30
2. hours:minutes:seconds,如1:20:30
3. days-hours:minute:seconds,如2-18:20:30
该脚本将会同时处理这三种格式的时间信息,并最终转换为进程所流经的分钟数。
/> cat > test11.sh
#!/bin/sh
#1. 通过ps命令获取所有进程的pid、etime和comm数据。
#2. 再通过grep命令过滤,只获取init进程的数据记录,这里我们可以根据需要替换为自己想要监控的进程名。
#3. 输出结果通常为:1 09:42:09 init
pid_string=`ps -eo pid,etime,comm | grep "init" | grep -v grep`
#3. 从这一条记录信息中抽取出etime数据,即第二列的值09:42:09,并赋值给exec_time变量。
exec_time=`echo $pid_string | awk '{print $2}'`
#4. 获取exec_time变量的时间组成部分的数量,这里是3个部分,即时:分:秒,是上述格式中的第二种。
time_field_count=`echo $exec_time | awk -F: '{print NF}'`
#5. 从exec_time变量中直接提取分钟数,即倒数第二列的数据(42)。
count_of_minutes=`echo $exec_time | awk -F: '{print $(NF-1)}'`
#6. 判断当前exec_time变量存储的时间数据是属于以上哪种格式。
#7. 如果是第一种,那么天数和小时数均为0。
#8. 如果是后两种之一,则需要继续判断到底是第一种还是第二种,如果是第二种,其小时部分将不存在横线(-)分隔符分隔天数和小时数,否则需要将这两个时间字段继续拆分,以获取具体的天数和小时数。对于第二种,天数为0.
代码如下 | 复制代码 |
if [ $time_field_count -lt 3 ]; then count_of_hours=0 count_of_days=0 else count_of_hours=`echo $exec_time | awk -F: '{print $(NF-2)}'` fields=`echo $count_of_hours | awk -F- '{print NF}'` if [ $fields -ne 1 ]; then count_of_days=`echo $count_of_hours | awk -F- '{print $1}'` count_of_hours=`echo $count_of_hours | awk -F- '{print $2}'` else count_of_days=0 fi fi |
#9. 通过之前代码获取的各个字段值,计算出该进程实际所流经的分钟数。
#10. bc命令是计算器命令,可以将echo输出的数学表达式计算为最终的数字值。
代码如下 | 复制代码 |
elapsed_minutes=`echo "$count_of_days*1440+$count_of_hours*60+$count_of_minutes" | bc` echo "The elapsed minutes of init process is" $elapsed_minutes "minutes." CTRL+D /> ./test11.sh The elapsed minutes of init process is 577 minutes. |
十二、模拟简单的top命令:
这里用脚本实现了一个极为简单的top命令。为了演示方便,我们在脚本中将很多参数都写成硬代码,你可以根据需要更换这些参数,或者用更为灵活的方式替换现有的实现。
/> cat > test12.sh
#!/bin/sh
#1. 将ps命令的title赋值给一个变量,这样在每次输出时,直接打印该变量即可。
header=`ps aux | head -n 1`
#2. 这里是一个无限循环,等价于while true
#3. 每次循环先清屏,之后打印uptime命令的输出。
#4. 输出ps的title。
#5. 这里需要用sed命令删除ps的title行,以避免其参与sort命令的排序。
#6. sort先基于CPU%倒排,再基于owner排序,最后基于pid排序,最后再将结果输出给head命令,仅显示前20行的数据。
#7. 每次等待5秒后刷新一次。
代码如下 | 复制代码 |
while : do clear uptime echo "$header" ps aux | sed -e 1d | sort -k3nr -k1,1 -k2n | head -n 20 sleep 5 done CTRL+D /> ./test12.sh 21:55:07 up 13:42, 2 users, load average: 0.00, 0.00, 0.00 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 6408 2.0 0.0 4740 932 pts/2 R+ 21:45 0:00 ps aux root 1755 0.2 2.0 96976 21260 ? S 08:14 2:08 nautilus 68 1195 0.0 0.4 6940 4416 ? Ss 08:13 0:00 hald postfix 1399 0.0 0.2 10312 2120 ? S 08:13 0:00 qmgr -l -t fifo -u postfix 6021 0.0 0.2 10244 2080 ? S 21:33 0:00 pickup -l -t fifo -u root 1 0.0 0.1 2828 1364 ? Ss 08:12 0:02 /sbin/init |