详解shell脚本(十二)—— 进阶功能实现

时间:2022-04-18 08:56:05

压缩或解压缩JavaScript

#filename: sample.js
function sign_out()
{
$("#loading").show();
$.get("log_in",{logout:"True"}),
function(){
window.location="";
});
}

下面是压缩JavaScript所需要完成的工作:

(1) 移除换行符和制表符;

(2) 压缩空格;

(3) 替换注释 /内容/。

要解压缩或者恢复JavaScript的可读性,我们则需要:

 用 ;\n 替换;;

 用 {\n 替换 {,\n} 替换}。

压缩

cat sample.js | \
tr -d '\n\t' | tr -s ' ' \
| sed 's:/\*.*\*/::g' \
| sed 's/ \?\([{}();,:]\) \?/\1/g'

解压缩

cat obfuscated.txt | sed 's/;/;\n/g; s/{/{\n\n/g; s/}/\n\n}/g'
#或者
cat obfuscated.txt | sed 's/;/;\n/g' | sed 's/{/\n\n/g' | sed 's/}/\n\n}/g'

该脚本在使用上存在局限:它会删除本不该删除的空格。假如有下列语句:

var a = “hello world”

两个空格会被转换成一个。这种问题可以使用我们讲解过的模式匹配工具

来解决。如果需要处理关键JavaScript代码,最好还是使用功能完善的工具

来实现。

通过执行下面的步骤来进行压缩。

(1) 移除 ‘\n’ 和 ‘\t’:

tr -d ‘\n\t’

(2) 移除多余的空格:

tr -s ’ ‘或者sed ‘s/[ ]+/ /g’

(3) 移除注释:

sed ‘s:/.*/::g’

因为我们需要使用 /* 和 */,所以用:作为sed的定界符,这样就不必对 / 进行转义了。

在 sed 中被转义为 \。

.* 用来匹配 /* 与 */ 之间所有的文本。

(4) 移除{、}、(、)、;、:以及,前后的空格。

sed ‘s/ \?([{}();,:]) \?/\1/g’

上面的sed语句含义如下。

 sed代码中的 / \?([{ }();,:]) \?/ 用于匹配,/\1/g 用于替换。

 ([{ }();,:]) 用于匹配集合 [ { }( ) ; , : ](出于可读性方面的考虑,

在这里加入了空格)中的任意一个字符。(和)是分组操作符,用于记忆所匹配的内容,以便在替换部分中进行向后引用。对(和)转义之后,它们便具备了另一种特殊的含义,进而可以将它们作为分组操作符。位于分组操作符前后的 \? 用来匹配可能出现在字符集合前后的空格。

 在命令的替换部分,匹配字符串(也就是一个可选的空格、一个来自字符集的字符再加一个可选的空格)被匹配的子字符串所替换。对于匹配的子字符串使用了向后引用,并通过分组操作符()记录了匹配内容。可以用符号 \1向后引用分组所匹配的内容。

解压缩命令工作方式如下:

 s/;/;\n/g 将;替换为;\n;

 s/{/{\n\n/g 将 { 替换为 {\n\n;

 s/}/\n\n}/g 将 } 替换为 \n\n}。
通过监视用户登录找出入侵者

我们可以编写一个shell脚本,对日志文件进行扫描并从中收集所需要的信息。为了处理SSH登录失败的情况,还得知道用户认证会话日志会被记录在日志文件/var/log/auth.log中。脚本需要扫描这个日志文件来检测出失败的登录信息,执行各种检查来获取所需要的数据。我们可以用host命令找出IP地址所对应的主机。

#!/bin/shell
#文件名:intruder_detect.sh
#用途:入侵报告工具,以auth.log作为日志文件
AUTHLOG= /var/log/auth.log

if [[ -n $1 ]];
then
AUTHLOG=$1
echo Using log file : $AUTHLOG
fi

LOG=/tmp/valid.$$.log
grep -V "invalid" $AUTHLOG > $LOG
users=$(grep "Failed password" $LOG | awk '{ print $(NF-5) }' | sort | uniq)
printf "%-5s|%-10s|%-10s|%-13s|%-33s|%s\n" "Sr#" "User" "Attempts" "IP address" "Host_Mapping" "Time range"

ucount=0;

ip_list="$(egrep -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" $LOG | sort |uniq)"
for ip in $ip_list;
do
grep $ip $LOG > /tmp/temp.$$.log
for user in $users;
do
grep $user /tmp/temp.$$.log> /tmp/$$.log
cut -c-16 /tmp/$$.log >$$.time
tstart=$(head -l $$.time);
start=$(date -d "$tstart" "+%s");
tend=$(tail -l $$.time);
end=$(date -d "$tend" "+%s")

limit=$(( $end - $start ))

if [ $limit -gt 120 ];
then
let ucount++;

IP=$(egrep -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" /tmp/$$.log | head -1 );
TIME_RANGE="$tstart-->$tend"
ATTEMPTS=$(cat /tmp/$$.log|wc-1);
HOST=$(host $IP | awk '{ print $NF }')

printf "%-5s|%-10s|%-10s|%-10s|%-33s|%-s\n" "$ucount" "$user" "$ATTEMPTS" "$IP" "$HOST" "$TIME_RANGE";
fi
done
done

rm /tmp/vaild.$$.log/tmp/$$.log $$.time/tmp/temp.$$.log 2>/dev/null

在intruder_detect.sh脚本中,我们将auth.log文件作为输入。也可以用脚本的命令行参数来提供一个日志文件作为输入,或者默认读取/var/log/auth.log。我们只需要记录合法用户的登录日志详情。如果有非法用户企图登录,则类似Failed password for invalid user bob from203.83. 248.32 port 7016 ssh2的日志就会出现在auth.log中。因此我们需要排除日志文件中所有包含invalid的行。grep命令的反转选项(-v)可以用来移除对应非法用户的所有日志内容。下一步是找出试图登录并且失败的用户列表。对于密码错误,SSH会记录类似的日志信息:sshd[21197]: Failed password for bob1 from 203.83.248.32 port 50035 ssh2,所以我们应找出所有包含“failed password”的行。

接下来,要找出所有不重复的IP地址以提取对应于每一个IP地址的日志行。提取IP地址列表可以用匹配IP地址的正则表达式和egrep命令来完成。用for循环对IP地址进行迭代,并用grep找出对应的日志行并将其写入临时文件。日志行中倒数第6个单词是用户名(例如bob1),可用awk命令提取用户名(倒数第6个单词)。NF返回最后一个单词的列号,因此NF -5就是倒数第6个单词的列号。我们再用sort和uniq生成一个没有重复的用户列表。

现在我们要收集表明登录失败的日志行,这些行中包含了用户名。for循环用来读取对应每一位用户的日志行,并将这些行写入临时文件。每一行的前16个字符是时间戳,可用cut命令进行提取。一旦得到了一个用户所有登录失败的时间戳,就要检查第一次和最后一次试图登录之间的时间差。第一行日志对应第一次登录,最后一行日志对应最后一次登录。我们用head -1提取首行,用tail -1提取末行。这样就有了首次登录(tstart)和末次登录(tends)的字符串格式的时间戳。使用date命令,我们可以将字符串形式的日期转换成Unix纪元时的总秒数(1.10节讲解了Unix纪元时)。

变量start和end中包含着秒数,分别对应于字符串形式的起始时间戳。对这两个时间求差,并检查差值是否大于2分钟(即120秒)。如果某个用户被确认为入侵者,其对应的细节信息就要生成日志。IP地址可以用正则表达式和egrep命令从日志中提取。试图登录的次数就是含有某个用户的日志行数。这个行数可以用wc命令获得。IP地址到主机名的映射可以将IP地址作为host命令的参数,然后从命令输出中提取。时间范围可以用已经提取到的时间戳来打印。最后,删除脚本使用过的临时文件。

上节的脚本旨在演示一个用于扫描日志并从中生成报告的模型。我们尽力让脚本更短小简单,以避免过于复杂,因此难免存在一些bug。你可以使用更好的处理逻辑来改进该脚本。

监视远程磁盘的健康情况

我们需要从网络中收集每台主机的磁盘使用情况信息,然后写入*主机的日志文件中。可以将负责收集信息并写入日志的脚本调度为每天的特定时间执行。可以使用SSH来登录远程系统收集磁盘使用情况。

首先得在网络中的所有远程主机上设置一个共用账户。这个账户供脚本disklog登录系统使用。我们需要为这个账户配置SSH自动登录(7.8节讲解了自动登录的配置方法)。假设在所有配置了自动登录的远程主机上都有一个叫做test的用户,那么来看看这个shell脚本:

#!/bin/bash
#文件名:disklog.sh
#desc:jianshi 远程系统的磁盘使用情况

logfile="diskusage.log"

if [[ -n $1 ]]
then
logfile=$1
fi

if [ ! -e $logfile ]
then
printf "%-8s %-14s %-9s %-8s %-6s %-6s %-6s %s\n" "Date" "IP address" "Device" "Capacity" "Used" "Free" "Percent" "Status" > $logfile
fi

IP_LIST="127.0.0.1 0.0.0.0"
#提供远程主机IP地址列表

(
for ip in $IP_LIST;
do
#slynux是用户名
ssh slynux@$ip 'df -H' | grep ^/dev/ > /tmp/$$.df
while read line;
do
cur_date=$(date +%D)
printf "%-8s %-14s " $cur_date $ip
echo $line | awk '{ printf("%-9s %-8s %-6s %-6s %-8s",$1,$2,$3,$4,$5); }'

pusg=$(echo $line | egrep -o "[0-9]+%")
pusg=$(pusg/\%/);
if [ $pusg -lt 80 ];
then
echo SAFE
else
echo ALERT
fi

done< /tmp/$$.df
done
) >> $logfile

我们可以用cron以固定的间隔来调度脚本执行,例如在crontab中写入以下条目,就可以在每天上午10点运行该脚本:

00 10 * * *  /home/path/disklog.sh  /home/user/diskusg.log

执行命令crontab -e,然后添加上面一行。也可以手动执行脚本

在脚本disklog.sh中,我们可以提供日志文件路径作为命令行参数,否则脚本使用默认的日志文件。如果日志文件不存在,它会将日志文件头部写入新文件中。-e logfile用来检查文件是否存在。远程主机的IP地址列表被存储在变量IP_LIST中,彼此之间以空格分隔。要确保在IP_LIST中列出的所有远程系统中都有用户test,并且SSH已经配置了自动登录。for循环用来对IP地址进行逐个迭代。通过ssh使用远程命令df -H获取磁盘剩余空间。这项数据被存储在一个临时文件中。while循环用来逐行读取这个文件。利用awk提取并打印数据,同时一并打印的还有日期。用egrep提取使用率,并将%删除以获取使用率的数值部分。检查得到的数值,看是否超过了80。如果不足80,将状态设置为SAFT;如果大于或等于80,则将状态设置为ALERT。打印出来的所有数据要被重定向到日志文件中。因此代码被放入子shell()中,并将标准输出重定向到日志文件。

找出系统中用户的活跃时段

考虑一个使用共享主机的Web服务器。每天都会有很多用户登录和注销,用户活动都被记入了服务器的系统日志。这则攻略是一项实践任务:利用系统日志找出每个用户在服务器上停留了多久,并根据其时间长短来进行分级,最后生成一份包含等级、用户名、首次登录时间、末次登录时间、登录次数以及总使用时长等细节信息的报告。让我们看看如何解决这个问题。

last命令用来列出系统中有关用户登录会话的细节。这些会话数据被存储在/var/log/wtmp文件中。通过分别累计各用户的会话时间,就能得出他们的总使用时间。

#!/bin/bash
#desc:查找活跃用户
log=/var/log/wtmp

if [[ -n $1 ]];
then
log=$1;
fi

printf "%-4s %-10s %-10s %-6s %-8s\n" "Rank" "User" "Start" "Logins" "Usage hours"
last -f $log | head -n -2 > /tmp/ulog.$$
cat /tmp/ulog.$$ | cut -d' ' -fi | sort | uniq> /tmp/users.$$

(
while read user;
do
grep ^$user /tmp/ulog.$$ > /tmp/user.$$
minutes=0

while read t
do
s=$(echo $t | awk -F: '{ print ($1 * 60) + $2 }')
let minutes=minutes+s
done< <(cat /tmp/user.$$ | awk '{ print $NF }' | tr -d ')(')
firstlog=$(tail -n 1 /tmp/user.$$ | awk '{ print $5,$6 }')
nlogins=$(cat /tmp/user.$$ | wc -l)
hours=$(echo "$minutes / 60.0" | bc)
printf "%-10s %-10s %-6s %-8s\n" $user "$firstlog" $nlogins $hours done< /tmp/user.$$

) | sort -nrk 4 | awk '{ printf("%-4s %s\n",NR, $0) }'
rm /tmp/users.$$ /tmp/user.$$ /tmp/ulog.$$

在脚本active_users.sh中,需要提供日志文件作为命令行参数,否则就使用默认的日志文件wtmp。命令last -f用来打印日志文件的内容。日志文件的第一列是用户名。我们用cut从中提取第一列,然后用sort和uniq找出不重复的用户。对于每一位用户,用grep找出其对应登录会话的日志行并写入一个临时文件。日志的最后一列是用户登录会话的时长。为了找出用户总的使用时间,需要累加所有的会话时长。使用时间的格式是(小时:秒),需要用一个简单的awk脚本将其转换成分钟。

要提取用户的会话时长,得使用awk命令。要移除括号,得使用tr -d。用<( COMMANDS )操作符将使用时长字符串列表作为标准输入传递给while循环,其作用就相当于文件输入。利用date命令将每一个时长字符串转换成秒数,并累加到变量seconds中。把出现在最后一行的用户的首次登录时间提取出来。登录次数就是日志的行数。要根据总的使用时间来计算每位用户的等级,数据记录以使用时长为键,进行降序排列。用sort命令的-nr选项指定按照数值逆序排列。-k4指定键的列号(即使用时长)。最后,sort的输出被传递给awk。awk命令为每一行添加上行号,这个行号就是每一位用户的等级。