Chapter11 - 编写BASH脚本
Bash shell 脚本编写基础知识
Bash 脚本编写基础知识
可能通过管理员的众多Linux 命令行工具来完成很多简单的日常系统管理任务。但是,更复杂的任务通常需要将多个命令链接到一起。在这些情况下,Linux 命令行工具可以与Bash shell 的功能组合使用,以创建强大的 shell 脚本来解决实际问题。
形式最简单的 Bash shell 就是一个由命令列表构成的可执行文件。但是编写良好,shell 脚本自身独立执行时也可以变为强大的命令行工具,甚至由其他脚本进一步利用。
精通 shell 脚本编写是所有操作环境中 Linux 系统管理员的成功要素。 shell 脚本编写的工作知识在企业环境中尤为关键,因为使用 shell 脚本编写可以提高完成日常任务的效率和准确性。
选择编程语言
尽管 Bash shell 脚本编写可用于完成很多任务,但可能不是适用于所有情况的工具。管理员的酌情使用各种编程语言,如 C、C++、Perl、Python、Ruby 和其他编程语言。每种编程语言都具有其优缺点,因此,没有任何一种编程语言是适用于所有情况的工具。
对于主要可以通过调用其他命令行实用工具来完成的任务,Bash shell 脚本是一种不错的选择。如果任务涉及到大数据处理和操作,那么其他语言(如 Perl 或 Python) 将适合于工作。尽管 Bash 支持算术运算,但仅限于简单的整数算术。对于更复杂的算术运算,应考虑使用 C 或 C++ ,如果解算需要数组,那么 Bash 可能不是最佳工具。Bash 对一维数组的支持已经有一段时间,并且最新版本甚至支持关联数组。但是,Perl 或 Python 具有更好的数组功能:能够适用多给数组。
随着管理员开始精通 shell 脚本编写,他们将更加深入了解其功能和局限性。这种经验结合其他编程语言的长期接触,将使管理员能够更好的理解每种语言的优缺点以及哪种语言最适合哪些问题。
创建和执行Bash shell 脚本
可以通过在文本编辑器中打开新的空文件来创建Bash shell 脚本。尽管可以使用任何文本编程器,但是高级编辑器(如 vim 或 cmacs )理解 Bash shell 语法并且可以提供颜色编码的高亮显示。这种高亮显示对于发现语法错误(如不成对的引号、未括起来的其他常见失误)是一种巨大帮助。
命令解释器
Bash shell 脚本的第一行以 #! 开头,通常也称为 sharp-bang 或缩写版本 sha-bang 。这种两字节表示法在技术上称为幻数模式。其表示文件是执行的 shell 脚本。
后面的路径名称是命令解释器,也就是应用于执行脚本的程序。由于Bash shell 脚本将由 Bash shell 解释,因此它们以下面的第一行开头。
#!/bin/bash
……
执行 Bash shell 脚本
在编写 了Bash shell 脚本之后,需要修改其文件权限和所有权,以使其变为可执行文件。可使用 chmod 命令修改执行权限,并且可能与 chown 命令组合以相应更改脚本的文件所有权。仅应将执行权限授予脚本所面向的用户。
一旦某个 Bash shell 脚本变为可执行,可通过在命令行中输入其名称来调用。如果只输入了脚本文件的基本名称, Bash 将搜索在 shell 的 PATH 环境变量中指定的目录,以查找与该名称匹配的可执行文件的第一个实例。管理员应避免脚本名称与其他可执行文件匹配,还应确保在其系统上正确配置 PATH ,以便脚本将成为 shell 找到第一个匹配项。which 命令(后跟可执行脚本的文件名)显示当作命令调用脚本名称时,执行的脚本所驻留的目录。
[root@rhel ~]# which hello
/root/bin/hello
[root@rhel ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/ibutils/bin:/root/bin
[root@rhel ~]#
显示输出
通过将文本作为参数传递给命令,echo 命令可用于显示任何文件。默认情况下,文件定向到标准输出(STDOUT),但是也只可以使用输出重定向将其定向到标准错误 (STDERR)。在下面的简单Bash 脚本中,echo 命令向 STDOUT 显示消息“Hello,world”。
[root@promote bin]# cat hello
#!/bin/bash
echo "Hello,World"
[root@promote bin]# ./hello
Hello,World
[root@promote bin]#
尽管 echo 命令的功能看上去很简单,但由于该命令可用于各种用途,因此在 shell 脚本中广泛使用,其通常用于在脚本执行期间显示参考消息或错误消息。这些消息可能是儿童的的脚本进度指示符,并且可以定向到标准输出、标准错误或者重定向到日志文件以进行存档。显示错误消息时,一种良好的做法是将他们定向到 STDERR 以便更容易区分错误消息与正常状态消息。
[root@promote bin]# cat hello
#!/bin/bash
echo "Hello World"
echo "ERROR:Houston,we have a problem." >&2 ##将输入定向到STDERR
[root@promote bin]# ./hello 2>hello.log
Hello World
[root@promote bin]# cat hello.log
ERROR:Houston,we have a problem.
当尝试调试有问题的 shell 脚本时,echo 命令可能也很有帮助。向行为不同于预期的脚本部分中添加 echo 语句可帮助阐明正在执行的命令以及正在调用的变量的值。
对特殊符号加引号
一些字符或单词在特定上下文中对 Bash shell 具有特殊含义。某些情况下,需要的是这些字符的字面值,而非其特殊含义。例如,Bash 将 # 字符解释为注释的开关,因此该字符以及同一行中该字符后面的所有内容都将被忽略。如果不需要这种特殊含义,则需要告知Bash ,#字符将被视为字面值。可以使用转义字符 \ 、 单引号 ' ' 或双引号 " " 来禁用特殊字符或单词的含义。
转义字符 \ 可取消紧跟在该字符后面的单个字符的特殊含义。例如,要使用 echo 命令显示字面值字符串 # test ,Bash 不能使用特殊含义来解释 # 字符 。转义字符可以放在 # 字符的前面以禁用其特殊含义。
[root@promote ~]# echo # not a comment
[root@promote ~]# echo \# not a comment
# not a comment
[root@promote ~]#
转义字符 \ 仅取消单个字符的特殊含义。当文本字符串中的多个字符需要转义时,用户可以多次使用转义字符或采用单引号 ' ' 。单引号保留其括起的所有字符的字面含义。以下示例演示了在需要转义多个字符时如何使用单引号。
[root@promote ~]# echo # not a comment #
[root@promote ~]# echo \# not a comment #
# not a comment
[root@promote ~]# echo \# not a comment \#
# not a comment #
[root@promote ~]# echo '# not a comment #'
# not a comment #
[root@promote ~]#
单引号保留其括起的所有字符的字面值,而双引号的区别在于双引号不保留美元符号 $ 、反引号 “ 和反斜杠 \ 的字面值。使用双引号括起时,美元符号和反引号保留其特殊含义,并且仅当反斜杠字符面前是美元符号、反引号、双引号、反斜杠或换行符时,才会保留反斜杠字符的特殊含义。
[root@promote ~]# echo '$HOME'
$HOME
[root@promote ~]# echo '`pwd`'
`pwd`
[root@promote ~]# echo '"Hello,world"'
"Hello,world"
[root@promote ~]# echo "$HOME"
/root
[root@promote ~]# echo "`pwd`"
/root
[root@promote ~]# echo ""Hello,world""
Hello,world
[root@promote ~]# echo "\$HOME"
$HOME
[root@promote ~]# echo "\`pwd\`"
`pwd`
[root@promote ~]# echo "\"Hello,world\""
"Hello,world"
[root@promote ~]#
使用变量
随着 shell 脚本复杂性的增加,使用变量通常会很有帮助。变量充当容器,在这一容器, shell 脚本可以在内存中存储数据。通过变量,可以在脚本执行期间轻松访问和修改存储数据。
为变量分配值
通过以下语法将数据作为值分配给变量:
VARIABLENAME=value
尽管变量通常是大写字母,它们可以由数字、字母(大写和小写)和下划线 _ 组成。但是变量名称不能以数字开头。等号 = 用于为变量赋值,并且不能用空格将其与变量名称或值分开。以下是有效变量名称的一些示例:
COUNT=40
first_name=John
file1=/tmp/abc
_ID=RH123
变量中存储的两种常见数据类型是整数值和字符串值。为变量分配字符串值时,一种良好做法是用引号将其括起,这是因为,如果未将字符串值在单引号或双引号中,那么 Bash 会将空格字符解释为单词分隔符。应用使用单引号或双引号括起变量值取决于应如何处理对 Bash 具有特殊含义的字符。
full_name='John Doe'
full_name="$FIRST #LAST"
price='$1'
扩展变量值
通过在变量名称前面加上美元符号$
,可以通过称为变量扩展的过程来重新调用变量的值。例如,可以使用 $VARIABLENAME
来引用 VARIABLENAME
变量的值。$VARIABLENAME
语法是花括号引起的变量扩展形式 ${VARIABLENAME}
的简化版本。尽管简化形式通常也可接受,但在某些情况下,必须使用花括号引起的形式以消除歧义并避免意外结果。
在以下示例中,如果不使用花括号,Bash 会将 $FIRST_$LAST
解释为变量 $FIRST_
后面跟变量$LAST
,而不是由_
字符分隔的变量 $FIRST
和 $LAST
.因此,在此情况下,必须使用花括号引起的形式才能使变量扩展正确运行。
[change@rhel ~]$ FIRST_=Jane
[change@rhel ~]$ FIRST=John
[change@rhel ~]$ LAST=Doe
[change@rhel ~]$ echo $FIRST_$LAST
JaneDoe
[change@rhel ~]$ echo ${FIRST}_${LAST}
John_Doe
[change@rhel ~]$
使用 Bash shell 扩展功能
除了变量扩展外,Bash shell
还提供了几种其他类型的 shell
扩展功能。其中,命令替换和算术扩展在编写 Bash shell
脚本时很有用,并且也很常用。
命令替换
命令替换可将命令调用替换为执行命令后的输出。该功能允许在新上下文中使用命令的输出,如另一个命令的参数、变量的值以及循环结构的列表。
可以使用将命令括在反引号中的旧形式来调用命令替换,如 <COMMAND>
。但是,首选方法是使用最新的 $( )
语法,即 $(<COMMAND>)
。
[change@rhel ~]$ echo "Current time:`date`"
Current time:Thu Mar 28 21:34:53 CST 2019
[change@rhel ~]$ echo "Current time:$(date)"
Current time:Thu Mar 28 21:35:11 CST 2019
首选使用最新语法是因为最新语允许嵌套命令替换。在下面的嵌套命令示例中, find 命令的输出用作 tar 命令的参数,而tar 命令随后将其输出存储到变量 TAROUTPUT 中。
[root@rhel ~]# TAROUTPUT=$(tar -cvf /tmp/incremental_bakcup.tar $(find /etc -type f -mtime -30))
tar: Removing leading `/' from member names
[root@rhel ~]# echo $TAROUTPUT
/etc/systemd/system/CmsGoAgent.service
算数扩展
Bash 的算术扩展可用于执行简单的整数算术运算,并且使用语法$[<EXPRESSION>]
。用$[]
括起时,算术表达式将由 Bash 进行求值,然后替换为求值结果。Bash 将首先对括起的表达工执行变量扩展和命令替换,再求值。与命令行替换一样,允许嵌套算术替换。
[change@rhel ~]$ echo $[1+1]
2
[change@rhel ~]$ echo $[2*2]
4
[change@rhel ~]$ COUNT=1;echo $[$[$COUNT+1]*2]
4
[change@rhel ~]$
算术扩展中使用表达式中允许使用空格字符。使用空格字符可以改善复杂表达式中的可读性或者包含变量时的可读性。
[change@rhel ~]$ SEC_PER_MIN=60
[change@rhel ~]$ MIN_PER_HR=60
[change@rhel ~]$ HR_PER_DAY=24
[change@rhel ~]$ SEC_PER_DAY=$[$SEC_PER_MIN * $MIN_PER_HR * $HR_PER_DAY]
[change@rhel ~]$ echo "There are $SEC_PER_DAY seconds in a day."
There are 86400 seconds in a day.
[change@rhel ~]$
以下是算术表达式中常用的部分运算符及其含义。
运算符 | 含义 |
---|---|
<VARIABLE> ++ | 变量后置递增(let a++) |
<VARIABLE> – | 变量后置递减(let a–) |
++<VARIABLE> | 变量前置递增(let ++a) |
–<VARIABLE> | 变量前置递减(let –a) |
– | 一元减法 |
+ | 一元加法 |
** | 求幂 |
* | 乘法 |
/ | 除法 |
% | 求余 |
+ | 加法 |
– | 减法 |
当表达式中存在多个运算符时,Bash 将根据运算符的优先级,按顺序对特定运算符进行求值。例如,乘法和除法的优先级高于加法和减法。如果所需的求值顺序不同于默认优先级,则可以使用圆括号来对子表达式进行分组。
[change@rhel ~]$ echo $[ 1+1*2]
3
[change@rhel ~]$ echo $[(1+1)*2]
4
[change@rhel ~]$
下表按照从最高到最低优先级顺序列出了常用的算术运算符。优先级相同的运算符一起列出。
运算符 | 含义 |
---|---|
<VARIABLE> ++、<VARIABLE> – | 变量后置递增和递减 |
<++VARIABLE>、<–VARIABLE> | 变量前置递增和递减 |
-、+ | 一元减法和加法 |
** | 求幂 |
*、/、% | 乘法、除法、求余 |
+ 和 – | 加法、减法 |
迭代 for 循环
系统管理员在其日常活动中经常会遇到重复任何。重复任何可能表现为对目标多冷执行某个操作,例如在 10 分钟内,每隔一分钟检查进程来查看其是否完成。任何重复还可能表现为一次对多个目标执行某个操作,例如对系统上的每个数据库进行数据库备份。for 循环是 Bash 提供的多个 shell 循环结构之一,并且可用于任务迭代。
使用 for 循环
Bash 的 for 循环结构使用以下语法。循环按顺序逐一独处理 <LIST>
中提供的项目,并且在处理列表中的最后一个项目之后退出。列表中的每个项目临时存储为 <VARIABLE>
的值,而 for 循环执行包含在其结构中的命令块。变量的命令是任意的。通常,变量值由命令块中的命令进行引用。
for <VARIABLE> in <LIST>;do
<COMMAND>
……
<COMMAND> referencing <VARIABLE>
done
可以通过多种方式来为 for 循环提供项目列表。可以是用户直接输入的项目的列表,或者是通过不同类型的 shell 扩展生成,如变量扩展、文件名扩展和命令替换。下面部分示例演示了可以向 for 循环提供列表的不同方式。
[change@rhel ~]$ for HOST in host1 host2 host3;do echo $HOST;done
host1
host2
host3
[change@rhel ~]$ for HOST in host{1,2,3};do echo $HOST;done
host1
host2
host3
[change@rhel ~]$ for HOST in host{1..3};do echo $HOST;done
host1
host2
host3
[change@rhel ~]$ for FILE in file*;do ls $FILE;done
filea
fileb
filec
[change@rhel ~]$ for FILE in file{a..c};do ls $FILE;done
filea
fileb
filec
[change@rhel ~]$ for PACKAGE in $(rpm -qa | grep kernel);do echo "$PACKAGE was installed on $(date -d @$(rpm -q --qf "%{INSTALLTIME}\n" $PACKAGE))";done
kernel-3.10.0-693.21.1.el7.x86_64 was installed on Thu May 3 22:00:57 CST 2018
kernel-3.10.0-514.26.2.el7.x86_64 was installed on Fri Aug 18 11:57:47 CST 2017
kernel-tools-libs-3.10.0-957.5.1.el7.x86_64 was installed on Tue Feb 19 11:50:30 CST 2019
kmod-kernel-mft-mlnx-4.7.0-1.rhel7u3.x86_64 was installed on Thu Oct 19 20:01:34 CST 2017
kernel-devel-3.10.0-957.5.1.el7.x86_64 was installed on Tue Feb 19 11:50:42 CST 2019
kernel-3.10.0-514.el7.x86_64 was installed on Fri Aug 18 11:51:58 CST 2017
kernel-headers-3.10.0-957.5.1.el7.x86_64 was installed on Tue Feb 19 11:49:29 CST 2019
kmod-mlnx-ofa_kernel-4.1-OFED.4.1.1.0.2.1.gc22af88.rhel7u3.x86_64 was installed on Thu Oct 19 20:00:18 CST 2017
kernel-tools-3.10.0-957.5.1.el7.x86_64 was installed on Tue Feb 19 11:50:30 CST 2019
mlnx-ofa_kernel-devel-4.1-OFED.4.1.1.0.2.1.gc22af88.rhel7u3.x86_64 was installed on Thu Oct 19 20:01:34 CST 2017
kernel-3.10.0-957.5.1.el7.x86_64 was installed on Tue Feb 19 11:51:55 CST 2019
kernel-devel-3.10.0-693.21.1.el7.x86_64 was installed on Thu May 3 20:13:33 CST 2018
mlnx-ofa_kernel-4.1-OFED.4.1.1.0.2.1.gc22af88.rhel7u3.x86_64 was installed on Thu Oct 19 20:00:17 CST 2017
[change@rhel ~]$ for EVEN in $(seq 2 2 8);do echo "$EVEN";done;echo "Who do we appreciate?"
2
4
6
8
Who do we appreciate?
[change@rhel ~]$
shell 脚本错误故障排除
编写、使用或维护 shell 脚本的管理员不可避免地会遇到脚本的错误。错误通常是由于输入错误、语法错误或脚本逻辑不会导致。
处理 shell 脚本编写错误的是一种不错的方法就是在编写脚本期间,尽力预防错误的发生。如前所述,在编写脚本时,将文本编辑器与 Bash 语法高亮显示结合可以帮助使错误更明显。避免在脚本中引入错误的另一种简单方法是在创建脚本期间遵循良好的做法。
良好风格的做法
使用注释有助于向读者阐明脚本的用途和逻辑。每个脚本顶部应包含注释,用于概述脚本的用途、预期操作和一般逻辑。还可以在脚本中通篇使用注释以阐明关键部分,特别是可能导致困惑的部分。注释不仅有助于其他用户阅读和调试脚本,通常还将在经过一段时间后帮助作者回忆脚本的编写过程。
将脚本内容结构化以改进可读性。只要语法正确,命令解释器将无缺陷地执行脚本中的命令,绝对与其结构或格式无关。以下是要遵循的一些良好做法:
- 将长命令分解为多选更小的代码块。代码段越短,就超便于读者领悟和理解。
- 将多个语句的开关和结尾排好,以便于查看控制结构的开始和结束位置以及它们是否正确关闭。
- 对包含多选语句的行进行缩进,以表示代码逻辑的层次结构和控制结构的流程。
- 使用行间距分隔命令块以阐明一个代码段何时结束以及另一个代码段何时开始。
- 在整个脚本中通篇使用一致的格式。
利用这些简单做法,极大方便了用户在编写期间发现错误,还提高了脚本对于将来读者的可读性。以下示例演示添加注释和空格如何能够极大改善脚本可读性。
#!/bin/bash
for PACKAGE in $(rpm -qa | grep kernel); do echo "$PACKAGE was installed on $(date -d @$(rpm -q -qf %{INSTALLTIME}\n $PACKAGE))";done
#!/bin/bash
#
#This script provides information regarding when kernel-related packages
#are installed on a system by querying infomation from the RPM database.
#
#Variable
PACKAGETYPE=kernel
PACKAGES=$(rpm -qa | grep $PACKAGETYPE)
#Loop through packages
for PACKAGE in $PACKAGES;do
#Determine package install date and time
INSTALLEPOCH=$(rpm -q -qf "%{INSTALLTIME}\n" $PACKAGE)
#RPM reports time in epoch,so need to convert
#it to date and time format with date command
INSTALLDATETIME=$(date -d @$INSTALLEPOCH)
#Print message
echo #PACKAGES was installed on $INSTALLDATETIME"
done
请勿对脚本采取的操作的结果进行假设。特别是对于脚本的输入尤为如此,如命令行参数、用户的输入、命令替换、变量扩展和文件名扩展。不要进行有关这些输入的完整性的假设,而应竭力使用正确的引号并进行完整性检查。
对脚本外部的实体采取操作时,同样应慎重。这包括与文件交互以及调用外部命令。与文件和目录交互时,利用 Bash 的大量文件和目录测试,当发生意外错误时,请对命令的出口状态进行错误检查,而不是指望其成功并盲目地继续运行脚本。
为排除假设而采取的额外步骤将增加脚本的稳健性,且使其不会轻易偏离轨道,进而对系统造成意外和不必要的损坏。一些看似无害的代码行(如下面的代码行)可能会对命令执行结果和文件名扩展作出风险很高的假设。如果目录更改失败(由于目录权限或目录不存在),将对非预期目录中未知文件的列表执行后续的文件删除。
cd $TMPDIR
rm *
最后,尽管用心良苦的管理员在编写脚本时可能采用良好的做法,但并非所有管理员都始终会对良好做法的要素达成一致。管理员应遵循对自己和他们有利的行为方式,并且在其整个脚本中始终应用其做法。他们还应该考虑周到,并且在涉及到其他人编写的脚本中的编程样式时,还应了解个体差异。修改他人的脚本时,管理员应遵循原作者使用的现有结构、格式和做法,而不是对某一部分脚本施加自己的风格并破坏脚本的一致性,继而损坏了脚本的可读性和将来的可维护性。
调试模式和详细模式
如果尽了最大努力,脚本中还是引入错误,管理员会发现Bash 的调试模式非常有用。要在脚本上激活调试模式,请向脚本第一行中的命令解释器添加 -x 选项。
#!/bin/bash -x
以调试模式运行脚本的另一个方法是使用 -x 选项,作为Bash 的参数执行脚本。
[student@server0 bin]$ bash -x <SCRIPTNAME>
Bash 的调试模式将打印出脚本执行前由脚本执行的命令。已执行的所有 shell 扩展的结果都将显示在打印输出中。以下示例显示了在激活调试模式时显示的额外输出。
[student@server0 ~]$ cat filesize
#!/bin/bash
DIR=/home/student/tmp
for FILE in $DIR/*;do
echo "File $FILE is $(stat --printf='%s' $FILE) bytes."
done
[student@server0 ~]$ ./filesize
File /home/student/tmp/filea is 3 bytes.
File /home/student/tmp/fileb is 4 bytes.
File /home/student/tmp/filec is 4 bytes.
[student@server0 ~]$ bash -x ./filesize
+ DIR=/home/student/tmp
+ for FILE in '$DIR/*'
++ stat --printf=%s /home/student/tmp/filea
+ echo 'File /home/student/tmp/filea is 3 bytes.'
File /home/student/tmp/filea is 3 bytes.
+ for FILE in '$DIR/*'
++ stat --printf=%s /home/student/tmp/fileb
+ echo 'File /home/student/tmp/fileb is 4 bytes.'
File /home/student/tmp/fileb is 4 bytes.
+ for FILE in '$DIR/*'
++ stat --printf=%s /home/student/tmp/filec
+ echo 'File /home/student/tmp/filec is 4 bytes.'
File /home/student/tmp/filec is 4 bytes.
[student@server0 ~]$
尽管Bash 的高度模式提供了有帮助的信息,但大量的输出实际上可能会对故障排除直到阻碍而非帮助作用,特别是随着脚本长度的增加。幸运的是,可以仅仅对脚本的某一部分而非整个脚本局部启用调试模式。如果高度一个较长的脚本并且问题的来源已被缩小到脚本的某一部分,这一功能特别有用。
可以通过插入命令 set -x 和插入命令 set +x 以分别在脚本中的特定位置开启和关闭调试,以下演示表明,先前示例脚本中仅为 for 循环中括起的命令行启用了调试。
[student@server0 ~]$ cat filesize
#!/bin/bash
DIR=/home/student/tmp
for FILE in $DIR/*;do
set -x
echo "File $FILE is $(stat --printf='%s' $FILE) bytes."
set +x
done
[student@server0 ~]$ ./filesize
++ stat --printf=%s /home/student/tmp/filea
+ echo 'File /home/student/tmp/filea is 3 bytes.'
File /home/student/tmp/filea is 3 bytes.
+ set +x
++ stat --printf=%s /home/student/tmp/fileb
+ echo 'File /home/student/tmp/fileb is 4 bytes.'
File /home/student/tmp/fileb is 4 bytes.
+ set +x
++ stat --printf=%s /home/student/tmp/filec
+ echo 'File /home/student/tmp/filec is 4 bytes.'
File /home/student/tmp/filec is 4 bytes.
+ set +x
[student@server0 ~]$
除了调试模式外,Bash还提供了详细模式,可以通过 -v 选项来调用该模式。在详细模式中,Bash 在每个命令执行前将每个命令打印到标准输出。
[student@server0 ~]$ cat filesize
#!/bin/bash
DIR=/home/student/tmp
for FILE in $DIR/*;do
echo "File $FILE is $(stat --printf='%s' $FILE) bytes."
done
[student@server0 ~]$ bash -v ./filesize
#!/bin/bash
DIR=/home/student/tmp
for FILE in $DIR/*;do
echo "File $FILE is $(stat --printf='%s' $FILE) bytes."
done
stat --printf='%s' $FILE) bytes."
stat --printf='%s' $FILE) bytes.
stat --printf='%s' $FILE
File /home/student/tmp/filea is 3 bytes.
stat --printf='%s' $FILE) bytes."
stat --printf='%s' $FILE) bytes.
stat --printf='%s' $FILE
File /home/student/tmp/fileb is 4 bytes.
stat --printf='%s' $FILE) bytes."
stat --printf='%s' $FILE) bytes.
stat --printf='%s' $FILE
File /home/student/tmp/filec is 4 bytes.
[student@server0 ~]$
与调试功能一样,可以通过插入 set -v 和 set +v 行以分别在脚本中特定位置开户和关闭详细功能。
###课后练习
#!/bin/bash
#make new accounts
#
#Variable
NEWUSERFILE=/tmp/support/newusers
#Loop
for ENTRY in $(cat $NEWUSERFILE);do
#Extract first ,last ,and titer fieds
FIRSTNAME=$(echo $ENTRY | cut -d: -f1)
LASTNAME=$(echo $ENTRY | cut -d: -f2)
TIER=$(echo $ENTRY | cut -d: -f4)
#
#make accout name
FIRSTINITIAL=$(echo $FIRSTNAME | cut -c 1 | tr 'A-Z' 'a-z')
LOWERLASTNAME=$(echo $LASTNAME | tr 'A-Z' 'a-z')
ACCTNAME=$FIRSTINITIAL$LOWERLASTNAME
#Create user
useradd $ACCTNAME -c "$FIRSTNAME $LASTNAME"
done
TOTAL=$(cat $NEWUSERFILE | wc -l)
TITER1COUNT=$(grep -c :1$ $NEWUSERFILE)
TITER2COUNT=$(grep -c :2$ $NEWUSERFILE)
TITER3COUNT=$(grep -c :3$ $NEWUSERFILE)
TITER1PCT=$[ $TITER1COUNT * 100 / $TOTAL ]
TITER2PCT=$[ $TITER2COUNT * 100 / $TOTAL ]
TITER3PCT=$[ $TITER3COUNT * 100 / $TOTAL ]
#Print report
echo "\"Tier 1\",\"$TITER1COUNT\",\"$TITER1PCT%\""
echo "\"Tier 2\",\"$TITER2COUNT\",\"$TITER2PCT%\""
echo "\"Tier 3\",\"$TITER3COUNT\",\"$TITER3PCT%\""