Bash脚本实用元素与脚本语言对比
1. Bash中的算术运算
Bash主要处理文本,如命令、参数和文件名等。不过,它也能计算常见的算术表达式,只需将表达式用双括号
((expression))
括起来。由于许多算术字符(如
*
、
(
和
)
)会被shell特殊解析,若要在脚本中将参数作为数学表达式处理,最好给参数加上引号。
例如,创建一个名为
arith
的脚本:
#!/bin/bash
answer=$(( $* ))
echo $answer
运行脚本:
admin@server1:~$ ./arith "(8+1)*(7-1)-60"
-6
admin@server1:~$ ./arith "2**60"
1152921504606846976
最新版本的Bash支持64位整数(范围是 -9223372036854775808 到 9223372036854775807),旧版本仅支持32位整数(范围是 -2147483648 到 2147483647),且不支持浮点数。若脚本需要处理浮点数或更高级的运算符,可使用外部程序,如
bc
。
在算术表达式中,使用变量时无需加
$
符号:
#!/bin/bash
a=$1
b=$(( a+2 ))
echo "$a + 2 = $b"
c=$(( a*2 ))
echo "$a * 2 = $c"
运行脚本:
admin@server1:~$ ./arithexp 6
6 + 2 = 8
6 * 2 = 12
2. if语句
根据测试结果,可执行不同的代码块。Bash使用
if ... fi
语法,还可选择使用
elif
(else if)和
else
部分:
if expression1 ; then
(commands)
elif expression2 ; then
(commands)
...
elif expressionN ; then
(commands)
else
(commands)
fi
行尾的
; then
也可写成在下一行单独的
then
:
if expression
then
(commands)
fi
示例:
admin@server1:~$ if [[ -x hello ]]
> then
> echo "hello is executable"
> fi
hello is executable
下面是一个更复杂的脚本,用于在
/etc/passwd
文件中查找账户名:
#!/bin/bash
USERID="$1"
DETECTED=$( egrep -o "^$USERID:" < /etc/passwd )
if [[ -n "${DETECTED}" ]] ; then
echo "$USERID is one of us :-)"
else
echo "$USERID is a stranger :-("
fi
将此脚本命名为
friendorfoe
并使其可执行,然后测试:
admin@server1:~$ ./friendorfoe root
root is one of us :-)
admin@server1:~$ ./friendorfoe sasquatch
sasquatch is a stranger :-(
3. 简单脚本的故障排除
有一个用于删除文件或目录的脚本
delete
存在问题:
#!/bin/bash
if rm $1
then
echo file $1 deleted
else
if rmdir $1
then
echo directory $1 deleted
fi
fi
运行结果:
admin@server1:~$ ./delete hello2
file hello2 deleted
admin@server1:~$ ./delete hello2
rm: cannot remove `hello2': No such file or directory
rmdir: `hello2': No such file or directory
admin@server1:~$ mkdir hello3
admin@server1:~$ ./delete hello3
rm: cannot remove `hello3': Is a directory
directory hello3 deleted
下面是修复步骤:
1. 使用I/O重定向将结果保存到日志和错误文件中。
2. 捕获
rm
命令的返回值以生成成功或失败消息。
3. 捕获当前日期和时间并包含在输出日志中。
修复后的脚本
removefiles
:
#!/bin/bash
# removefiles deletes either files or directories
echo "$0 ran at" $(date) >> delete.log
if rm $1 2>> delete-err.log
then
echo "deleted file $1" >> delete.log
elif rmdir $1 2>> delete-err.log
then
echo "deleted directory $1" >> delete.log
else
echo "failed to delete $1" >> delete.log
fi
此脚本仍有不足,它未检查文件是否存在,也未区分文件和目录。可使用内置运算符进一步修复:
#!/bin/bash
# removefiles deletes either files or directories
echo "$0 ran at" $(date) >> delete.log
if [ ! -e $1 ]
then
echo "$1 does not exist" >> delete.log
elif [ -f $1 ]
then
echo -n "file $1 " >> delete.log
if rm $1 2>> delete-err.log
then
echo "deleted" >> delete.log
else
echo "not deleted" >> delete.log
fi
elif [ -d $1 ]
then
echo "directory $1 " >> delete.log
if rmdir $1 2>> delete-err.log
then
echo "deleted" >> delete.log
else
echo "not deleted" >> delete.log
fi
fi
若文件名包含空格,需在脚本中再次对其加引号:
#!/bin/bash
# removefiles deletes either files or directories
echo "$0 ran at" $(date) >> delete.log
if [ ! -e "$1" ]
then
echo "$1 does not exist" >> delete.log
elif [ -f "$1" ]
then
echo -n "file $1 " >> delete.log
if rm "$1" 2>> delete-err.log
then
echo "deleted" >> delete.log
else
echo "not deleted" >> delete.log
fi
elif [ -d "$1" ]
then
echo -n "directory $1 " >> delete.log
if rmdir "$1" 2>> delete-err.log
then
echo "deleted" >> delete.log
else
echo "not deleted" >> delete.log
fi
fi
4. 循环
若要多次执行某操作,需使用循环。Bash有三种循环:
for
、
while
和
until
。
4.1 for循环
for arg in list
do
commands
done
示例:
admin@server1:~$ for stooge in moe larry curly
> do
> echo $stooge
> done
moe
larry
curly
admin@server1:~$ for file in *
> do
> ls -l $file
> done
-rw-r--r-- 1 admin admin 48 2006-08-26 14:12 hello
admin@server1:~$ for file in $(find / -name \*.gif)
> do
> cp $file /tmp
> done
4.2 while循环
while expression
do
stuff
done
示例:
#!/bin/bash
MAX=100
((cur=1)) # Treat cur like an integer
while ((cur < MAX))
do
echo -n "$cur "
((cur+=1)) # Increment as an integer
done
4.3 until循环
until expression
do
stuff
done
示例:
#!/bin/bash
gameover="q"
until [[ $cmd == $gameover ]]
do
echo -n "Your commmand ($gameover to quit)? "
read cmd
if [[ $cmd != $gameover ]]; then $cmd; fi
done
使用
break
可跳出循环,使用
continue
可跳过本次循环的剩余部分并回到循环开头。
5. cron作业
Shell脚本常用于组合程序,在Linux中,常见的用途是定义cron作业。cron是标准的Linux作业调度器,可让任务在指定时间执行。
编辑cron作业需编辑
crontab
文件,查看内容:
admin@server1:~$ crontab -l
no crontab for admin
编辑
crontab
:
admin@server1:~$ crontab -e
crontab
文件每行包含时间规范和命令,格式如下:
| 字段 | 范围 | 说明 |
| ---- | ---- | ---- |
| minute | 0 - 59 | 分钟 |
| hour | 0 - 23 | 小时(24小时制) |
| day_of_month | 1 - 31 | 每月的天数 |
| month | 1 - 12 或 月份名 | 月份 |
| day_of_week | 0 - 7(0或7为周日,6为周六)或 星期名 | 星期 |
| command | - | 要执行的命令 |
示例:
5 * * * * rm /tmp/*.gif # remove all GIF files every 5 minutes
5 * * * * rm -v /tmp/*.gif >> /tmp/gif.log # the same, logged
为避免收到cron作业的邮件通知,可将标准输出和标准错误重定向:
command > /dev/null 2>&1
6. 脚本语言对比
Shell主要用于运行命令和展开文件名模式,执行算术计算等其他任务较困难,因为需防止文本被分词和
*
展开。复杂的Shell脚本中,括号、方括号等符号会堆积如山。
如今,可使用更高级的脚本语言完成任务,原因如下:
- 一些应用(如
adduser
和
apt-get
)已自动化了传统的Shell脚本任务。
- Shell脚本扩展性差,难以维护。
- Shell脚本运行速度慢。
- Shell语法复杂。
接下来将使用不同语言编写一个应用,在
/etc/passwd
文件中搜索信息。
7.
/etc/passwd
文件格式
/etc/passwd
文件通常包含系统账户、应用账户和用户账户,字段以冒号分隔:
| 字段 | 说明 |
| ---- | ---- |
| 账户名 | 账户的名称 |
| 加密密码 | 若使用
/etc/shadow
,则为
x
|
| 用户ID(uid) | 用户的唯一标识 |
| 组ID(gid) | 用户所属组的标识 |
| 全名或描述 | 用户的全名或描述信息 |
| 主目录 | 用户的主目录路径 |
| Shell | 用户登录时使用的Shell |
我们主要关注第五个字段(全名或描述),在Unix中,该字段被称为
gecos
字段。
8. 脚本版本
下面将逐步编写在
/etc/passwd
文件中搜索信息的脚本。
8.1 简单的Bash脚本
#!/bin/bash
grep -i "$1" /etc/passwd
将此脚本命名为
finduser.sh
并使其可执行:
admin@server1:~$ chmod +x finduser.sh
admin@server1:~$ ./finduser.sh alf
adedarc:x:500:500:Alfredo de Darc:/home/adedarc:/bin/bash
8.2 限制搜索到
gecos
字段
#!/bin/bash
pattern=$1
IFS=":"
while read account password uid gid name directory shell
do
# Exact case-sensitive matches only!
if [[ $name == $pattern ]]; then
echo "$account:$password:$uid:$gid:$name:$directory:$shell"
fi
done < /etc/passwd
8.3 增加大小写不敏感匹配
#!/bin/bash
pattern=$1
IFS=":"
while read account password uid gid name directory shell
do
if [[ $(echo $name | egrep -i -c "$pattern") -gt 0 ]]; then
echo "$account:$password:$uid:$gid:$name:$directory:$shell"
fi
done < /etc/passwd
8.4 限制搜索到用户ID大于500的账户
#!/bin/bash
pattern=$1
IFS=":"
while read account password uid gid name directory shell
do
# Exact matches only!
if [[ $uid -gt 500 && $(echo $name | egrep -i -c "$pattern") -gt 0 ]]; then
echo "$account:$password:$uid:$gid:$name:$directory:$shell"
fi
done < /etc/passwd
综上所述,通过本文我们了解了Bash脚本中算术运算、条件语句、循环、cron作业的使用,以及如何编写在
/etc/passwd
文件中搜索信息的脚本。同时,我们也看到了Shell脚本的局限性,以及使用更高级脚本语言的优势。在实际应用中,可根据具体需求选择合适的脚本语言和方法。
Bash脚本实用元素与脚本语言对比
9. 不同脚本语言的实现
9.1 Perl脚本
首先,我们来看一个简单的Perl脚本,用于在
/etc/passwd
文件中搜索指定字符串:
#!/usr/bin/perl
use strict;
use warnings;
my $pattern = $ARGV[0];
open(my $passwd_fh, '<', '/etc/passwd') or die "Can't open /etc/passwd: $!";
while (my $line = <$passwd_fh>) {
if ($line =~ /$pattern/i) {
print $line;
}
}
close($passwd_fh);
操作步骤:
1. 将上述代码保存为一个
.pl
文件,例如
finduser_perl.pl
。
2. 给脚本添加执行权限:
chmod +x finduser_perl.pl
。
3. 运行脚本:
./finduser_perl.pl alf
接下来,我们将搜索范围限制到
gecos
字段:
#!/usr/bin/perl
use strict;
use warnings;
my $pattern = $ARGV[0];
open(my $passwd_fh, '<', '/etc/passwd') or die "Can't open /etc/passwd: $!";
while (my $line = <$passwd_fh>) {
my @fields = split(/:/, $line);
my $gecos = $fields[4];
if ($gecos =~ /$pattern/i) {
print $line;
}
}
close($passwd_fh);
最后,我们进一步限制搜索到用户ID大于500的账户:
#!/usr/bin/perl
use strict;
use warnings;
my $pattern = $ARGV[0];
open(my $passwd_fh, '<', '/etc/passwd') or die "Can't open /etc/passwd: $!";
while (my $line = <$passwd_fh>) {
my @fields = split(/:/, $line);
my $uid = $fields[2];
my $gecos = $fields[4];
if ($uid > 500 && $gecos =~ /$pattern/i) {
print $line;
}
}
close($passwd_fh);
9.2 PHP脚本
下面是一个简单的PHP脚本,用于在
/etc/passwd
文件中搜索指定字符串:
<?php
$pattern = $argv[1];
$passwd_file = file('/etc/passwd', FILE_IGNORE_NEW_LINES);
foreach ($passwd_file as $line) {
if (stripos($line, $pattern) !== false) {
echo $line . PHP_EOL;
}
}
?>
操作步骤:
1. 将上述代码保存为一个
.php
文件,例如
finduser_php.php
。
2. 运行脚本:
php finduser_php.php alf
将搜索范围限制到
gecos
字段的PHP脚本:
<?php
$pattern = $argv[1];
$passwd_file = file('/etc/passwd', FILE_IGNORE_NEW_LINES);
foreach ($passwd_file as $line) {
$fields = explode(':', $line);
$gecos = $fields[4];
if (stripos($gecos, $pattern) !== false) {
echo $line . PHP_EOL;
}
}
?>
进一步限制搜索到用户ID大于500的账户的PHP脚本:
<?php
$pattern = $argv[1];
$passwd_file = file('/etc/passwd', FILE_IGNORE_NEW_LINES);
foreach ($passwd_file as $line) {
$fields = explode(':', $line);
$uid = $fields[2];
$gecos = $fields[4];
if ($uid > 500 && stripos($gecos, $pattern) !== false) {
echo $line . PHP_EOL;
}
}
?>
9.3 Python脚本
简单的Python脚本,用于在
/etc/passwd
文件中搜索指定字符串:
import sys
pattern = sys.argv[1]
with open('/etc/passwd', 'r') as f:
for line in f:
if pattern.lower() in line.lower():
print(line.strip())
操作步骤:
1. 将上述代码保存为一个
.py
文件,例如
finduser_python.py
。
2. 运行脚本:
python finduser_python.py alf
将搜索范围限制到
gecos
字段的Python脚本:
import sys
pattern = sys.argv[1]
with open('/etc/passwd', 'r') as f:
for line in f:
fields = line.strip().split(':')
gecos = fields[4]
if pattern.lower() in gecos.lower():
print(line.strip())
进一步限制搜索到用户ID大于500的账户的Python脚本:
import sys
pattern = sys.argv[1]
with open('/etc/passwd', 'r') as f:
for line in f:
fields = line.strip().split(':')
uid = int(fields[2])
gecos = fields[4]
if uid > 500 and pattern.lower() in gecos.lower():
print(line.strip())
10. 不同脚本语言的优缺点对比
| 脚本语言 | 优点 | 缺点 |
|---|---|---|
| Bash |
- 与系统命令集成良好,适合简单的系统管理任务。
- 无需额外安装,在Linux系统中广泛存在。 |
- 算术运算和复杂逻辑处理较困难。
- 扩展性和可维护性较差。 |
| Perl |
- 强大的文本处理能力,正则表达式支持优秀。
- 有丰富的模块库。 | - 语法相对复杂,代码可读性可能较差。 |
| PHP |
- 易于学习和上手,有大量的内置函数。
- 适合Web开发和简单的脚本任务。 | - 主要用于Web环境,在系统管理方面的功能相对较弱。 |
| Python |
- 语法简洁,代码可读性高。
- 有丰富的第三方库,可用于各种领域。 | - 运行速度相对较慢。 |
11. 总结与建议
在选择脚本语言时,需要根据具体的任务需求来决定:
- 如果是简单的系统管理任务,如文件操作、进程管理等,Bash脚本是一个不错的选择。
- 对于复杂的文本处理和正则表达式匹配,Perl可能更合适。
- 如果任务与Web开发相关,或者需要快速实现一个简单的脚本,PHP是一个好的选择。
- 对于数据处理、机器学习等复杂任务,Python是首选,其丰富的库可以大大提高开发效率。
通过本文,我们详细了解了Bash脚本的各种实用元素,包括算术运算、条件语句、循环、cron作业等,同时对比了不同脚本语言在处理
/etc/passwd
文件搜索任务时的实现方式和优缺点。希望这些内容能帮助你在实际应用中选择合适的脚本语言和方法。
下面是一个简单的mermaid流程图,展示了在
/etc/passwd
文件中搜索信息的基本流程:
graph TD;
A[开始] --> B[读取搜索模式];
B --> C[打开 /etc/passwd 文件];
C --> D[逐行读取文件];
D --> E{是否匹配模式};
E -- 是 --> F[输出匹配行];
E -- 否 --> D;
D --> G{是否到达文件末尾};
G -- 否 --> D;
G -- 是 --> H[关闭文件];
H --> I[结束];
这个流程图清晰地展示了在
/etc/passwd
文件中搜索指定字符串的基本步骤,从读取搜索模式开始,逐行检查文件内容,输出匹配的行,直到文件结束。
超级会员免费看
2681

被折叠的 条评论
为什么被折叠?



