超凡魔力

君子善思,善假于物,而不物于物。

0%

参考资源

常用命令

拉取镜像
javac

run

以安装jenkins为例。

docker run -u root  --rm   -d   -p 8080:8080   -p 50000:50000   -v jenkins-data:/var/jenkins_home   -v /var/run/docker.sock:/var/run/docker.sock   jenkinsci/blueocean 

– rm 容器停止时,会删除

-p 8080:80 将主机的8080端口,映射到,docker暴露的80端口上

-v 主机目录跟虚拟docker目录映射关系。 Docker Volume

-v /var/run/docker.sock:/var/run/docker.sock

上面通过sock文件的映射,达到与docker服务进程通信的目的。

-v jenkins-data:/var/jenkins_home

使用jenkins-data 来映射/var/jenkins_home目录,如果没有,会自动创建该卷。

–name demo 名字

–rm 临时使用,用完就删除容器。

Docker的数据持久化主要有两种方式: 1、bind mount 2、volume.

  • host机器的目录路径必须为全路径(准确的说需要以/~/开始的路径),不然docker会将其当做volume而不是volume处理

  • 如果host机器上的目录不存在,docker会自动创建该目录

  • 如果container中的目录不存在,docker会自动创建该目录

  • 如果container中的目录已经有内容,那么docker会使用host上的目录将其覆盖掉

–network 指定网络

相关可参考networkd

使用python的临时环境

使用docker非常方便建立临时的开发环境。如下命令,利用-v 将当前开发目录映射到 容器中,然后执行文件。

sudo docker run -it --rm -v ~/go:/go python:3.6 python /go/a.py

使用golang临时环境

直接编译~/go下面的a.go 文件。

sudo docker run -it --rm -v ~/go:/go golang go build a.go

构建镜像的策略

构建镜像,可以docker commit ,docker build 两种方式构建。前者,是相当于,保存运行中的容器,为镜像。

build的方式,需要有Dockerfile文件。推荐编写该文件的方式是:

  • 先启动一个镜像,运行bash命令占住镜像。

    docker run --name centos -itd  centos:7.9.2009 bash
    # 如果需要用到本地文件,  使用docker cp到容器内
    # 或为了加速下载,本地其一个简单的http服务
  • 进入容器,执行命令构建

    docker exec -it centos bash
    
    # 一步步执行命令构建
    
    # 当所有都操作妥当,然后保存一下操作历史
    history > /tmp/docker.his.txt
    # 拷贝出操作历史
    docker cp centos:/tmp/docker.his.txt .
    #然后挑选出合理的步骤,将命令放到RUN 后
    # 清理操作 见安装后清理操作
    
  • 善用多阶段构建。

    将经常使用到的基础镜像,先构建出来,然后在此镜像之上,构建。收集多阶段构建的Dockerfile到一起。

安装后清理操作

rm -rf intel-compute-runtime  # 删除安装使用到的临时文件
apt-get remove gnupg wget apt-transport    -https -y  # 删除安装的不必要的垃圾文件
apt-get clean autoclean -y   # 清理
apt-get autoremove -y   
rm -rf /var/lib/apt/lists/*   # 删除apt缓存到的文件

docker安装Jenkins

官网推荐的docker安装命令如下:

docker run -u root  --rm   -d   -p 8080:8080   -p 50000:50000   -v jenkins-data:/var/jenkins_home   -v /var/run/docker.sock:/var/run/docker.sock   jenkinsci/blueocean 

其中,使用jenkins-data映射了/var/jenkins_home地址。

在安装的时候,需要找初始化密码,不确定,可以按如下方式查找:

find . -name *jenkins*
#通过上面方式大致找到映射的目录
#如 /var/lib/docker/volumes/jenkins-data/_data/war/images/jenkins.svg
#实际上 /var/lib/docker/volumes/jenkins-data/_data/  目录
# 即为容器的/var/jenkins_home目录

# 以下直接可查到密码
cd /var/lib/docker/volumes/jenkins-data/_data/secrets
cat initialAdminPassword

剩下的就是在8080端口(上面映射的地址),访问,按要求填写即可。

ubuntu获取docker

wget -qO- https://get.docker.com/ | sh

综上,一键安装shell如下:

docker run -u root  --rm   -d   -p 8080:8080   -p 50000:50000   -v jenkins-data:/var/jenkins_home   -v /var/run/docker.sock:/var/run/docker.sock   jenkinsci/blueocean 
# 以下直接可查到密码
echo "initialAdminPassword"
cat /var/lib/docker/volumes/jenkins-data/_data/secrets/initialAdminPassword

官方的手册

安装docker软件

使用一键脚本安装

wget -qO- https://get.docker.com | sh

在centos服务器上,好像必须要开启服务,ubuntu好像不需要

service docker start 

安装nginx

如果只安装nginx服务器,直接到docker这一步,不要先执行docker run.

docker pull nginx

mkdir -p ~/nginx/www ~/nginx/logs ~/nginx/conf

docker cp XXXXXX:/etc/nginx/nginx.conf ~/nginx/conf
#XXXXXX为名字或者id
docker run -d -p 8082:80 --name runoob-nginx-test-web -v ~/nginx/www:/usr/share/nginx/html -v ~/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v ~/nginx/logs:/var/log/nginx nginx

备注:-v 指定目录,最好用绝对路径。否则,参数报错。或者使用 $PWD代替. 。

安装php-fpm

docker pull php:5.6-fpm
docker run --name myphp-fpm -v ~/nginx/www:/www -d php:5.6-fpm
mkdir ~/nginx/conf/conf.d

~/nginx/conf/conf.d 目录下的文件,名字如 myweb.conf

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm index.php;
    }
    
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    
    location ~ \.php$ {
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /www/$fastcgi_script_name;
        include        fastcgi_params;
    }
}

运行nginx

docker run --name runoob-php-nginx -p 8083:80 -d \
    -v ~/nginx/www:/usr/share/nginx/html:ro \
    -v ~/nginx/conf/conf.d:/etc/nginx/conf.d:ro \
    -v ~/nginx/logs:/var/log/nginx \
    --link myphp-fpm:php \
    nginx

其中–link链接两个容器。

安装mysql

docker pull mysql:5.6

mkdir -p ~/mysql/data ~/mysql/logs ~/mysql/conf 

 docker run -p 3306:3306 --name mymysql -v ~/mysql/conf:/etc/mysql/conf.d -v ~/mysql/logs:/logs -v ~/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.6

备注:使用sed批量替换 ~ 代表真实的位置。

一键脚本

php环境搭配有问题,出现file not found.


makeDir(){
    mkdir -p ~/nginx/conf/conf.d
}

genNginxConf(){

cat > ~/nginx/conf/nginx.conf <<EOL

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
EOL
}


genPHPConf (){

cat > ~/nginx/conf/conf.d/myphp.conf <<EOL
server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm index.php;
    }
    
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    
    location ~ \.php$ {
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /www/$fastcgi_script_name;
        include        fastcgi_params;
    }
}
EOL

}

################################

makeDir
genNginxConf
genPHPConf


docker run --name myphp-fpm -v ~/nginx/www:/www -d php:5.6-fpm

docker run --name mynginx -p 8083:80 -d \
    -v ~/nginx/www:/usr/share/nginx/html:ro \
    -v ~/nginx/conf/conf.d:/etc/nginx/conf.d:ro \
    -v ~/nginx/logs:/var/log/nginx \
    --link myphp-fpm:php \
    nginx

composer使用

每种编程语言都有各自的包管理工具。比如Python使用Pip,nodejs使用npm,go直接用go get等。php之前使用pear来进项包管理,现在比较流行的是使用composer,所以很有必要学习composer来进项相关的包管理。
重要的概念:psr-4类加载规范、命名空间

但是,Composer 不是一个包管理器。默认情况下它不会在全局安装任何东西。因此,这仅仅是一个依赖管理。

资源

查找,发布composer的网站:https://packagist.org/login/

中文境像 https://www.phpcomposer.com/https://docs.phpcomposer.com/

常用命令

安装composer

curl -sS https://getcomposer.org/installer | php

或者

php -r "readfile('https://getcomposer.org/installer');" | php

你可以通过 --install-dir 选项指定 Composer 的安装目录(它可以是一个绝对或相对路径):

curl -sS https://getcomposer.org/installer | php -- --install-dir=bin

切换代理

composer自定义包

参考文章

核心就是,将自定义的包放到git上,然后在composer.json中添加依赖。如下测试,再执行composer update:

"require": {
	"pack/test":"dev-master"
    },
"repositories":[
	{
		"type":"git",
		"url":"https://github.com/BrucelLi/pack-test.git"
	}
    ]

示例:见com.zip代码例子。

自动加载的原理

深入解析 composer 的自动加载原理

PHP自动加载函数 __autoload(),但是其缺点是:全局函数,只能被定义一次。加载文件比较多,其业务逻辑将非常复杂、难以维护。所以在PHP 7.2.0中已经被废弃。

例子:

<?php

function __autoload($classname) {
        require_once ($classname . ".class.php");
}

自动加载需要解决两个问题:

1、如何根据一个类名确定(文件名及)文件存储的路径。

2、使用require/include加载文件。

新的解决方案:

答案就是使用一个 __autoload调用堆栈 ,不同的映射关系写到不同的 __autoload函数 中去,然后统一注册统一管理。

spl_autoload_register() 就是我们上面所说的__autoload调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当 PHP 找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。

昨天的时候,了解了鸟哥的框架yaf框架,然后看了,今天就来试试。
按照教程,我安装了yaf框架,自己不确定框架是否安装好了,使用php_info来显示已经安装的框架,(打错了,正确的为phpinfo函数)。在这个上面折腾了很久,也没有搞好。nginx服务器报500错误,但是没有日志,我实在不找不到在哪该。关键日志不能打开,确实很难调试。

正确打开php日志的方式:

# php.ini
display_errors = On
# 或者用下面的方式打开
ini_set('display_errors','On');

代码中:
error_reporting(E_ALL);
#然后重启php-fpm ,不重启,配置为能重新载入
# /etc/init.d/php-fpm restart 

具体参见:error-reporting函数

php中heredoc写法

在php中,遇到多行的文字,难免要用到多行文字。这个时候用heredoc是比较好的。之前也用过了,但是又踩到坑了,记录一下

。假设文件总共这6行。因为缺少php的结束标记,所以会在最后一行,加上?>结束标记,这样EOD;就变成了EOD;?>这样的标记,

反正会报错。这个时候,需要在末尾再填加一个新行,即可。

注意:

  • 下面<<<EOT后面不能有空格
  • 注意末尾的结束符必须靠边,其前面不能有空格
  • 此外,里面能添加字符。
<?php
$str = <<<EOD
Example of string
spanning multiple lines
using heredoc syntax.
EOD;

坑一

如上面的代码,若是单独放到一个文件,会自动添加 ?> 然后也会报错。

坑二

2020-06-15 13:32

有问题的代码如下:

<?php
namespace service;
/**
 * User: chaofml
 * Desc: 抓取运单、测试生成fake订单、生成分区表等关的操作
 * Date: 2020年6月12日
 * Time: 13:09 周五
 */



class RecordService
{
    /**  
    * 每个月初的时候,创建下个月的扫描记录表
    * @return mixed sql执行结果
    */ 
    public static function createTable(){
        //创建下一个月的表
        $date = date('Ym',strtotime('first day of +1 months'));
        // $date = date('Ymd');
        $sql = <<<EOL
        CREATE TABLE IF NOT EXISTS `yc_advert_records_$date` (
            `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
            `advert_id` int(10) unsigned NOT NULL COMMENT '广告ID',
            `platform_id` int(10) unsigned NOT NULL COMMENT '广告平台ID',
            `mailno` bigint(13) unsigned NOT NULL COMMENT '运单号',
            `visit_count` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '访问次数',
            `created` datetime NOT NULL COMMENT '创建时间',
            PRIMARY KEY (`id`) USING BTREE,
            KEY `mailno` (`mailno`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='广告访问记录';
    EOL;
        $result = db()->getPdo()->exec($sql);
        return $result;
    }
    /**  
    * 制造假数据,往redis推送数据
    * @return array ['len'=>'redis缓存的数据量',$mails=>[]]
    */ 
    public static function testPush(){
        
        $redis = cache();
        foreach(range(1,100) as $v){
            $mailno = '1:43052'.mt_rand(1000,9999).mt_rand(1000,9999);
            $mails[]=$mailno;
            $size = $redis->rPush('mailno_printer_record',$mailno);
        }
        $len = $redis->llen('mailno_printer_record');
        return ['len'=>$len,'mails'=>$mails];
    }
}

运行报错:

PHP Parse error:  syntax error, unexpected ']', expecting identifier (T_STRING) or variable (T_VARIABLE) or number (T_NUM_STRING) in /yd/shell/demo.php on line 46

说的是下面这行报错:

$mails[]=$mailno;   //让我以为php不支持[]语法呢 

解决方式将EOL符号前的空格去掉即可:

EOL;

解决过程思考:一开始我无法定位到错误,以为是php版本问题(确实php版本有影响到EOL的解析),但是将46行的代码抽离出来,发现又没有相关报错了。然后思考,是不是$redis引入的问题,是不是命令空间的问题,是不是类的问题(之前不是在类里面)。最后,才想到,分批次测试代码。然后不到1分钟,顺利定位到bug。

最简单而又使用的调试方式:

代码出错,又不确定哪块出错,哪何不从头一点点的添砖加瓦,每次测试通过,再添加一点代码。

坑三

在yii2项目,云课堂中遇到的。情况是:在windows的版本中没有任何警告,能正常运行。但是docker中的版本遇到了问题:

问题代码如下:

 [
     'class' => 'yii\grid\ActionColumn',
     'template' => <<<EOL
    <div>......</div>
EOL,
     'header' => '操作',
]

EOL那行更改如下,即可:

EOL
,

转载

https://t.ti-node.com/thread/6445811931310718977

其实前面是谈过一次daemon进程的,但是并涉及过多原理,但是并不影响使用。今天打算说说关于daemon进程更多的二三事,本质上说,如果你仅仅是简单实现利用一下daemon进程,这个不看也是可以的。
杠真,*NIX真是波大精深,越是深入看越是发现它的diao。原理往往都是枯燥的,大家都不爱看,但这并不影响我坚持写自己对这些东西的理解。
三个概念,理(bei)解(song)一下:
  • 进程组。一坨相关的进程可以组成一个进程组,每个进程组都会有一个组ID(正整数),每个进程组都会有一个组长进程,组长进程的ID等于进程组ID。组长进程可以创建新的进程组以及该进程组中的其他进程。一个进程组的是有生命周期的,即便是组长进程挂了,只有组里还有其他的活口,那就就算该进程组依然存活,只有到组里最后一个活口也挂了,那真的就是彻底没了。
  • 会话。一坨相关的进程组组成了一个会话。在*NIX下,是通过setsid()创建一个新的会话。但是值得注意的是,组长进程不能创建会话,简单理解就是在组长进程中,执行setsid函数会报错,这点很重要。所以一般都是组长进程执行fork,然后主进程退出,因为子进程的进程ID是新分配的,而子进程的进程组ID是继承父进程的,所以子进程就注定不可能是组长进程,从而可以确保子进程中一定可以执行setsid函数。在执行setsid函数时候,一般会发生下面三个比较重要的事情:
  • 该进程会创建一个新的进程组,该进程为进程组组长(或者你可以认为这是一种提升)
  • 该进程会创建一个会话组并成为该会话的会话首进程(会话首进程就是创建该会话的进程)
  • 该进程会失去控制终端。如果该进程本来就没有控制终端,则罢了(liao)。如果有,那么该进程也将脱离该控制终端,与之失去联系。
  • 控制终端。每个会话可能会拥有一个控制终端(看着比较玄学,你可以暂时理解为就一个那种黑乎乎的命令行窗口),建立与控制终端连接的会话首进程叫做控制进程。
结合Linux命令ps来查看一下上述几个概念的恩怨情仇,我们看下我们常用的 ps -o pid,ppid,pgid,sid,comm | less 执行结果:

第一行分别是PID,PPID,PGID,SID,COMMAND,依次分别是进程ID,该进程父进程ID,进程组ID,会话ID,命令。
通过最后一列,我们知道第二行就是bash也就是bash shell进程,其进程ID为15793,其父进程为13291,进程组ID为15793,会话ID也会15793,结合前面的概念,我们可以知道bash shell就是该进程组组长。
第三行则是ps命令的进程,其进程ID为15816,他是由于bash进程fork出来的,所以他的父进程ID为15793,然后是他所属的组ID为15816,所属的会话ID依然是15793。
最后一行是less命令的进程,其进程ID为15817,他也是由bash进程fork出来的,所以他的父进程ID也为15793,然后是他所属的组ID为15816,所属的会话ID依然是15793。
简单总结一下:
  • 上述三个进程一共形成了两个进程组,bash自己为一组,组ID为15793,组长进程为bash自己 ; ps和less为一组,组ID为15816,组长进程为ps进程
  • 上述三个进程属于同一个会话,会话ID为15793,会话首进程为bash进程(待定)
  • 控制终端则为打开的terminal窗口,与之关联的控制进程则为bash进程
通过这么一顿分析,是不是感觉可以接受点儿了?然后是,叨逼叨了半天这个,跟daemon进程有啥子关系?
啦啦啦,下面通过引入代码直接分析:
$pid = pcntl_fork();
if( $pid < 0 ){
  exit('fork error.');
} else if( $pid > 0 ) {
  // 主进程退出
  exit();
}
// 子进程继续执行

// 最关键的一步来了,执行setsid函数!
if( !posix_setsid() ){
  exit('setsid error.');
}

// 理论上一次fork就可以了
// 但是,二次fork,这里的历史渊源是这样的:在基于system V的系统中,通过再次fork,父进程退出,子进程继续,保证形成的daemon进程绝对不会成为会话首进程,不会拥有控制终端。

$pid = pcntl_fork();
if( $pid  < 0 ){
  exit('fork error');
} else if( $pid > 0 ) {
  // 主进程退出
  exit;
}

// 子进程继续执行

// 啦啦啦,啦啦啦,啦啦啦,已经变成daemon啦,开心
cli_set_process_title('testtesttest');
// 睡眠1000000,防止进程执行完毕挂了
sleep( 1000000 );
将上述文件保存为daemon.php,然后php daemon.php执行,使用 ps -aux | grep testte ,如果没有什么大问题你应该就可以看到这个进程在后台跑了。
所以为什么第一步要先fork呢?因为调用setsid的进程不可以是组长进程(篇头的枯燥知识需要了吧?),所以必须fork一次,然后将主进程直接退出,保留子进程。因为子进程一定不会是组长进程,所以子进程可以调用setsid。调用setsid则会产生三个现象:创建一个新会话并成为会话首进程,创建一个进程组并成为组长进程,脱离控制终端。
啦啦啦,明白为啥篇头那一坨枯燥的知识是为了什么吧?
然而,实际上,上述代码仅仅完成了一个标准daemon的80%,还有20%需要我们进一步完善。那么,需要完善什么呢?我们修改一下上述代码,让程序在最终的代码段中执行一些文本输出:
$pid = pcntl_fork();
if( $pid < 0 ){
  exit('fork error.');
} else if( $pid > 0 ) {
  // 主进程退出
  exit();
}
// 子进程继续执行

// 最关键的一步来了,执行setsid函数!
if( !posix_setsid() ){
  exit('setsid error.');
}

// 理论上一次fork就可以了
// 但是,二次fork,这里的历史渊源是这样的:在基于system V的系统中,通过再次fork,父进程退出,子进程继续,保证形成的daemon进程绝对不会成为会话首进程,不会拥有控制终端。

$pid = pcntl_fork();
if( $pid  < 0 ){
  exit('fork error');
} else if( $pid > 0 ) {
  // 主进程退出
  exit;
}

// 子进程继续执行

// 啦啦啦,啦啦啦,啦啦啦,已经变成daemon啦,开心
cli_set_process_title('testtesttest');
// 循环1000次,每次睡眠1s,输出一个字符test
for( $i = 1; $i <= 1000; $i++ ){
  sleep( 1 );
  echo "test".PHP_EOL;
}
将文件保存为daemon.php,然后php daemon.php执行文件,嗯,是不是有怪怪的现象,大概类似于下图:

即便你按Ctrl+C都没用,终端在不断输出test,唯一办法就是关闭当前终端窗口然后重新开一个,然而,这并不符合社会主义主流价值观。所以,我们还要解决标准输出和错误输出,我们的daemon程序不可以再将终端窗口当作默认的标准输出了。
其次是将当前工作目录修改更改为根目录。不然可能就会出现下面这样一个问题,就是如果父进程是的工作目录是一个挂载的目录,那么子进程会继承父进程的工作目录,当子进程已经daemon化后就会出现一个悲剧:那就是虽然原来挂载的目录已经不用了,但是却无法用umount卸载,非常悲剧。
最后一个问题是,要在第一次fork后设置umask(0),避免权限上的一些问题。所以较为完整的代码如下:
// 设置umask为0,这样,当前进程创建的文件权限则为777
umask( 0 );

$pid = pcntl_fork();
if( $pid < 0 ){
  exit('fork error.');
} else if( $pid > 0 ) {
  // 主进程退出
  exit();
}
// 子进程继续执行

// 最关键的一步来了,执行setsid函数!
if( !posix_setsid() ){
  exit('setsid error.');
}

// 理论上一次fork就可以了
// 但是,二次fork,这里的历史渊源是这样的:在基于system V的系统中,通过再次fork,父进程退出,子进程继续,保证形成的daemon进程绝对不会成为会话首进程,不会拥有控制终端。

$pid = pcntl_fork();
if( $pid  < 0 ){
  exit('fork error');
} else if( $pid > 0 ) {
  // 主进程退出
  exit;
}

// 子进程继续执行

// 啦啦啦,啦啦啦,啦啦啦,已经变成daemon啦,开心
cli_set_process_title('testtesttest');
// 一般服务器软件都有写配置项,比如以debug模式运行还是以daemon模式运行。如果以debug模式运行,那么标准输出和错误输出大多数都是直接输出到当前终端上,如果是daemon形式运行,那么错误输出和标准输出可能会被分别输出到两个不同的配置文件中去
// 连工作目录都是一个配置项目,通过php函数chdir可以修改当前工作目录
chdir( $dir );

参考资料

官网关于docker的资料

思考

在Dockerfile里面RUN命令,那么复杂,为什么很多人都没有将其专门放到一个build.sh脚本中,然后执行呢?而都是用 \ &&来续行?原因是什么?访问不到环境变量?构建参数?

Demo

springcloud

FROM 10.131.9.12:5000/base/jdk8-redis:1.0.0

RUN echo "Asia/Shanghai" > /etc/timezone && rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

COPY pro.jar  /work/pro.jar
ADD run.sh /

RUN chmod +x /run.sh
CMD ["/run.sh"]

FROM 指定基础镜像,必须是第一条指令,10.131.9.12:5000/base/jdk8-redis 表示具体的位置,1.0.0即tag标签。一般指定公有的源,上面是私有源的例子。

RUN 运行命令。 利用斜杆、&& 拼接多条指令。

1, shell 格式: RUN <命令>,就像直接在命令行中输入的命令一样

2, exec 格式:RUN [“可执行文件”,“参数1”,“参数2”],更像是函数调用中的格式

每一个RUN命令都会在 docker镜像中新建一层,所以应该尽量少用 RUN 命令,而且要在RUN 的最后要做必要的清除工作

COPY 复制文件。当前位置的文件,复制到镜像中的位置。

COPY 会将原文件的各种数据都保留,比如 读、写、执行权限,可以通过 –chown=: 选项来改变文件的所属用户及所属组。

思考:/work/pro.jar,如果/work目录不存在,是否会报错?如果拷贝的文件不存在,是否会报错?如果镜像中,已经有该文件,是强制替换还是提醒、报错?

答疑:1、对于基础镜像,如果没有该目录,COPY的时候,镜像会自动增加该目录。(已测,但权限可能保留原有的,最好增加修改权限的指令)。2、同名文件,会覆盖,不提示。但是为了确保安全,最好先删除。3、复制的源文件,如果不存在,则会报错。

ADD (复制?拷贝?)

高级版的COPY,会下载、会解压(tar.gz tar.xz等格式)。

和 COPY 指令的功能,性质基本一致 。但是针对url、tar包,会自动处理。如: 原路径为 tar 压缩包,如果压缩文件格式为 gzip , bzip2 以及 xz 的情况下,ADD 指令将自动解压这个压缩文件到 <目标路径> 去,只有此种情况适合使用 ADD 指令。

CMD 指定运行的命令。格式同RUN。数组,一个数组即代表一条完整的指令。[“命令”,”参数1”,”参数2”,”参数3”]。将所有打包时需要运行的命令组合,放到run.sh文件中,+x使该文件具有可执行权限,然后运行。CMD与RUN区别: RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。

命令格式也有两种,同RUN。 CMD 指令用于指定默认的容器主进程的启动命令。CMD可以启动多个进程,但是必须要有一个在前台。

思考:CMD命令如何含义?是指每次镜像启动时,都要运行的指令吗?另外,CMD命令,能否有多个?

run.sh文件如下:

#!/bin/sh

cd /work

if [ "${SW_AGENT_NAME}" != "" ] ; then
    java -javaagent:/work/agent/skywalking-agent.jar -jar $JAVA_OPT pro.jar
else
    java -jar $JAVA_OPT pro.jar
fi

weblogic-nginx

Dockerfile如下:

FROM 10.131.9.12:5000/base/weblogic-redis-nginx-autodeploy-apm:2.0.0
  
COPY nginx.conf /etc/nginx

ENV LANG zh_CN.utf8

WORKDIR /u01/oracle/autodeploy

ADD 项目目录 ./项目目录

ADD ./resouceDefine.py .
CMD nginx && ./startup.sh

ENV指定环境变量。

如:ENV JENKINS_HOME /opt/jenkins/data
RUN mkdir -p $JENKINS_HOME/plugins

WORKDIR指定工作目录。此设置会影响到?复制文件?运行文件?

CMD启动nginx,并运行startup.sh脚本(可能在镜像中已经存在),

思考:也可以不用数组?两种形式实际有如何区别?

具体参见weblogic-nginx.md

php73

Dockerfile如下:

FROM 10.131.9.12:5000/base/rhel_php:php73-base

RUN rm -rf /yd/* && yum install -y yd-php73-apm-4.1.7 php73-soap QConf php73-qconf nscd

COPY nginx.conf /usr/local/nginx/conf/nginx.conf
COPY php.ini /usr/local/php/etc/php.ini
COPY httpd.conf /etc/httpd24/httpd.conf

ADD yd /yd
RUN chown -R www:www /yd/*

EXPOSE 80

ADD run.sh /
CMD ["/run.sh"]

FORM 公司内部,基础镜像。版本:Red Hat Enterprise Linux Server release 6.7. (cat /etc/redhat-release 而ubuntu cat /etc/os-release)

RUN 2 先删除原有yd下的文件 ,删除成功后,才会安装相应的php配置。

思考:yum源内部的?

ADD 增加yd文件夹内容

RUN 7 递归更改新增加的内容所有者的权限。

EXPOSE 对外暴露80端口 【实际上基础镜像已经暴露过此端口】

最后两条ADD CMD,增加run.sh文件,但是未为该文件增加可执行权限?

思考:会报错吗? CMD命令会继承或覆盖吗?

其他:

run.sh文件如下:

#!/bin/sh
  
echo "Asia/Shanghai" > /etc/timezone
rm -rf /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

if [ $APP_PORT ];then
    sed -i "s/APP_PORT/$APP_PORT/g" /usr/local/nginx/conf/nginx.conf
else
    sed -i "s/APP_PORT/80/g" /usr/local/nginx/conf/nginx.conf
fi

if [ $YD_SESSION_REDIS_PORT ];then
    sed -i "1416c session.save_handler = redis" /usr/local/php/etc/php.ini
    sed -i "1417a session.save_path = \"$YD_SESSION_REDIS_PORT\"" /usr/local/php/etc/php.ini
else
    echo "Session save files"
fi

if [ $YD_PHP_APM ];then
   echo 'php_admin_value auto_prepend_file "/etc/apm.php"' >> /etc/httpd24/httpd.conf
   echo "extension=tideways.so" >> /usr/local/php/etc/php.ini
   echo "tideways.sample_rate=100" >> /usr/local/php/etc/php.ini
   echo "tideways.auto_prepend_library=0" >> /usr/local/php/etc/php.ini
else
   echo "NO PHP APM";
fi

touch /var/log/app.log
chown -R www:www /var/log/app.log
echo success > /var/log/app.log

#find /yd -name ".*" | xargs rm -rf

/usr/local/nginx/sbin/nginx
/usr/local/apache/bin/httpd -DFOREGROUND

注意:run.sh文件最后两行,启动了nginx跟httpd服务。CMD启动的进程,可以启动多个进程,但是必须要有一个进程在前台。最起码Pod是这样要求,否则会陷入无限的重启中。

作用:设置时区。根据环境变量$APP_PORT来设置nginx端口,默认80端口。 设置php中session保存位置 $YD_SESSION_REDIS_PORT。$YD_PHP_APM变量? 创建app.log日志文件,写入成功命令。开启nginx http服务。

备注:sed操作

# 全局替换,将a替换为b。g表示全局替换 -i 表示保存文件
sed -i "s/a/b/g"  test.txt
# 指定行数修改,空格分隔(或多个) 
sed -i "5c helloword" a.txt
# 增加新行,即在第5行后增加。
sed -i "5a zhangsan" a.txt
# 根据文件夹的后缀来替换pro.jar
sed -i "s/pro.jar/`ls |grep.sh$`/g" run.sh
# 打印5到10行的内容   -n参数  表示仅显示处理后的结果。
sed -n "5,10p" a.txt
# 查找指定内容,进行替换  \n插入新行。
# 如果一行匹配到两处,也仍然只插入一次。 /se/d 匹配到则删除。
sed -i '/se/a oneoneone\ntwotwotwo' test.txt

nginx配置如下:


user  www;
worker_processes  auto;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  10240;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    access_log off;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
    gzip              on;
    gzip_min_length   4096;
    gzip_buffers      4 8k;
    gzip_types        text/* text/css application/javascript application/x-javascript;
    gzip_comp_level   1;
    gzip_vary         on;
    gzip_http_version 1.1;

    upstream php {
        server 127.0.0.1:81;
    }

    server {
        listen       80;
        server_name  localhost;
        access_log   /dev/null;
	error_log    /dev/null;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /yd;
            index  index.php index.html index.htm;

            if (!-e $request_filename) {
                proxy_pass http://php;
            }
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        location ~ \.php$ {
            proxy_set_header  Host $host:APP_PORT;
            #proxy_set_header  X-Real-IP $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass   http://127.0.0.1:81;
        }
    }

}

nginx实际上,代理80端口,静态文件直接处理,php请求通过负载均衡交由apache处理(81端口),或者检测到.php扩展名,通过代理proxy_pass传递到81端口。在docker镜像中,整体只对外暴露80端口。

注意:APP_PORT在启动的时候,会被run.sh替换成具体的端口。

打包

有了Dockerfile之后,便可以打包。手动打包命令,

  • 方式一:
docker build -t runoob/ubuntu:v1 . 

-t 指定标签,标签内容runoob/ubuntu:v1

. 指定当前打包的目录。

  • 方式二:
docker build -f /path/to/a/Dockerfile .

-f 指定Dockerfile所在目录

  • 方式三:
docker build github.com/creack/docker-firefox

直接从git仓库打包。实际上该仓库,主要包含一个Dockerfile文件。

Dockerfile指令

如上文例子,打包指令大、小写不分区。默认全部大写,以示区分。可以使用#号进行注释。#注释,必须顶行写,否则会认为命令内容。

FROM

指定基础源。

VOLUME

指定卷标。参见 https://www.jianshu.com/p/ef0f24fd0674

在Dockerfile中,我们也可以使用VOLUME指令来申明contaienr中的某个目录需要映射到某个volume:

VOLUME /foo

这表示,在docker运行时,docker会创建一个匿名的volume,并将此volume绑定到container的/foo目录中,如果container的/foo目录下已经有内容,则会将内容拷贝的volume中。也即,Dockerfile中的VOLUME /foodocker run -v /foo alpine的效果一样。

Dockerfile中的VOLUME使每次运行一个新的container时,都会为其自动创建一个匿名的volume,如果需要在不同container之间共享数据,那么我们依然需要通过docker run -it -v my-volume:/foo的方式将/foo中数据存放于指定的my-volume中。

ADD

如下,一个报错提醒。

When using ADD with more than one source file, the destination must be a directory and end with a /

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

增加chown参数,避免再执行下面的命令。

RUN chown -R www-data:www-data /var/www/html/

ADD的理解,

ADD --chown=www-data:www-data phpmyadmin/  /var/www/html/

phpmyadmin是本地目录, /var/www/html/是目标目录,可以不存在,会自动创建。上面的命令含义,会将phpmyadmin文件夹下的所有目录直接拷贝到/var/www/html/目录下。

如果是tar文件,则会自动解压,tar文件下的所有内容,直接拷贝到/var/www/html/。相当于,在后者的目录中,直接运行tar解压而已。

如果是普通的文件,则相当于,直接拷贝到后者。

终上,文件夹内,看到什么,就是什么。

COPY

报错如下:

Step 4/8 : COPY target/* .
When using COPY with more than one source file, the destination must be a directory and end with a /

更正为 COPY target/ .

CMD

跟ENTERPOINT命令相似,放在文件的最后一行(多个CMD只有最后一个生效),主要用来指定,容器启动后执行的命令。此命令,相当于docker run -d image:tag [命令] 后的命令。

ONBUILD

为镜像添加触发器。触发器可以是任何指令。如父镜像中定义了ONBUILD触发器,在子镜像的构建中会执行,但是在孙子镜像中不会执行。

ONBUILD ADD . /var/www/

可以使用docker inspect 命令来查看详情。

ARGS

参考Dockerfile中的ARG指令详解

Dockerfile中的ARG指令用以定义构建时需要的参数,使用格式如下:

ARG set_va
ARG a_nother_name=a_default_value  

ARG指令定义的参数,在docker build命令中以–build-arg a_name=a_value形式赋值。如果docker build命令传递的参数,在Dockerfile中没有对应的参数,将抛出警告。 如果在Dockerfile中,ARG指令定义参数之前,就有其他指令引用了参数,则参数值为空字符串。

Docker自带的如下ARG参数,可以在其他指令中直接引用:

  • HTTP_PROXY

  • http_proxy

  • HTTPS_PROXY

  • https_proxy

  • FTP_PROXY

  • ftp_proxy

  • NO_PROXY

  • no_proxy

例子:

下面是自己编写的nginx/lua安装的教程,注意下面的定义形式,空格、引号等。

FROM 10.131.9.12:5000/base/nginx:1.16

ARG NGINX_VERSION="1.10.3"
ARG LuaJIT_VERSION="2.0.4"
ARG KIT_VERSION="0.3.0"
ARG Lua_Mod_VERSION="0.10.8"
ARG LuaJIT_PATH="/usr/local/luajit"


ADD nginx-${NGINX_VERSION}.tar.gz /tmp
ADD LuaJIT-${LuaJIT_VERSION}.tar.gz /tmp
ADD ngx_devel_kit-${KIT_VERSION}.tar.gz /opt
ADD lua-nginx-module-${Lua_Mod_VERSION}.tar.gz /opt

RUN cd /tmp/LuaJIT-${LuaJIT_VERSION} \  
    && make PREFIX=${LuaJIT_PATH} \
    && make install PREFIX=${LuaJIT_PATH} \
    && export LUAJIT_LIB=${LuaJIT_PATH}/lib \
    && export LUAJIT_INC=${LuaJIT_PATH}/include/luajit-2.0 \
    && cd /tmp/nginx-${NGINX_VERSION} \
    &&  ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-stream_ssl_module --with-http_realip_module  --add-module=/opt/ngx_devel_kit-${KIT_VERSION}  --add-module=/opt/lua-nginx-module-${Lua_Mod_VERSION}  \    
    &&  make \
    &&  make install \
    &&  rm -rf /tmp/nginx-${NGINX_VERSION} \
    &&  rm -rf /tmp/LuaJIT-${LuaJIT_VERSION} \
    &&  rm -rf /opt/ngx_devel_kit-${KIT_VERSION} \
    &&  rm -rf /opt/lua-nginx-module-${Lua_Mod_VERSION} \
    &&  echo "${LuaJIT_PATH}/lib" >> /etc/ld.so.conf.d/libc.conf \
    &&  echo "${LuaJIT_PATH}/include/luajit-2.0"  >> /etc/ld.so.conf.d/libc.conf \
    &&  ldconfig

COPY nginx.conf /usr/local/nginx/conf/nginx.conf

EXPOSE 80

ADD lua  /yd/lua
ADD demo /yd/demo

ADD run.sh /
RUN chmod u+x /run.sh
CMD ["/run.sh"]

选择性构建:

#一个参数
docker build -t nginx_plugin:2.3  --build-arg LuaJIT_VERSION=2.0.5 . 
#多个参数
docker build -t nginx_plugin:2.3  --build-arg LuaJIT_VERSION=2.0.5  --build-arg KIT_VERSION=0.3.1 . 
# 禁用缓存 --no-cache
docker build -t nginx_plugin:2.3  --build-arg LuaJIT_VERSION=2.0.5  --build-arg KIT_VERSION=0.3.1 --no-cache . 

注意,构建时,自定义的参数好像显示不出来,依然使用的是${变量命}的形式。但是,构建过程中,可以看到新建的目录等,确实有变化。重要:容易被缓存。

STOPSIGNAL

docker在1.9之后的dockerfile中新增关键字STOPSIGNAL。在github,官网,nginx的Dockerfile中见到的。

默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过–stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s 。

具体参见:地址

HEALTHCHECK

以下示例包含一个健康检查。另外,还有多阶段构建。

https://github.com/statping/statping/blob/dev/Dockerfile )其为健康检查的一个工具。state ping的缩写。

FROM statping/statping:base AS base
ARG BUILDPLATFORM
# Statping main Docker image that contains all required libraries
FROM alpine:latest
RUN apk --no-cache add libgcc libstdc++ ca-certificates curl jq && update-ca-certificates

COPY --from=base /go/bin/statping /usr/local/bin/
COPY --from=base /root/sassc/bin/sassc /usr/local/bin/
COPY --from=base /usr/local/share/ca-certificates /usr/local/share/

WORKDIR /app
VOLUME /app

ENV IS_DOCKER=true
ENV SASS=/usr/local/bin/sassc
ENV STATPING_DIR=/app
ENV PORT=8080

EXPOSE $PORT

HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD curl -s "http://localhost:$PORT/health" | jq -r -e ".online==true"

CMD statping --port $PORT

USER

USER mynewuser

WORKDIR

WORKDIR /home/mynewuser

跟docker run 中 -w的参数作用相同。

多阶段构建

参见HEALTHCHECK中的示例。

总结

构建镜像有两种方式。1、进入容器,更改容器后,commit容器的变化。2、跟据Dockerfile来进行打包。

Dockerfile方式构建,基于DSL(领域特定语言)的描述打包。构建过程,实际上是运行镜像,有部分指令是在构建过程中的容器中执行,如RUN ,有些可能是在容器启动有执行,如CMD、ENTERPOINT等。而ADD、COPY能将容器外部的文件添加到容器内部。

构建镜像过程中,要区分真机环境跟镜像运行环境。如RUN 后的指令,实际上操作的都是容器内的文件、环境。

参考文档

文件目录为Dockerfile所在目录

shell是个很强大的工具,界面操作,用其来简单,但是想做到自动化,却有点困难。而且,会将限制用户的输入,这是好处,也是坏出。增加了限制,可以保证执行的有效性,但也限制了功能。

开发界面,也需要一定的成本。而直接开发出不带界面的,可能更容易一些。命令行直接再用强大的管道符号、重定向符号,能完成非常复杂的功能。

另外,*匹配也非常的强大,觉得这个功能,是能是bash解释器的功能。有了它,我们甚至省了循环。而且还有匹配(搜索)特定位置的文件的功能。

还有就是{}序列功能。

shellcheck

脚本语法检测工具,方便测试出脚本中存在的错误。

apt-get install shellcheck

bash快捷方式

Bash 快捷键
命令 说明
ctrl + a 移到命令行首
ctrl + e 移到命令行尾
ctrl + f 按字符右移
ctrl + b 按字符左移
ctrl + u 从光标处(不包含)删除至命令行首(包含)
ctrl + k 从光标处(不包含)删除至命令行尾(包含)
ctrl + w 从光标处(不包含)删除至单词字首(包含)
ctrl + d 删除光标处的字符
ctrl + h 删除光标前的字符
ctrl + l 清屏
ctrl + c 终止命令
c+f 左移一个单词
esc+b 右移一个单词
ctrl + 左右箭头 单词移动

shell综合使用例子

删除 none标记的镜像,综合用了 grep awk xargs 工具。

sudo docker image ls|grep none  |awk '{print $3}' | xargs -n 1  sudo docker rmi

更好的方式如下:

docker image ls | awk '$1=="<none>" && $2=="<none>"{printf "docker rmi %s\n",$3}'|sh

跟据指定的名称,删除相关的一组进程。

ps aux | grep test.php | grep -v grep |awk '{print $2};'|xargs -n 1 kill 

查看cpu使用的占用率,前10.

#cpu
ps aux| sort -nk3 |head 
# 内存
ps aux| sort -nk4 |head 

去重后统计

根据第4列去重,查看有多少条记录。注意,可能会有空行。

awk '{print $4}' /etl/dta/0.csv |sort -u|wc -l

统计某个次出现的次数

查询某个ip在日志文件中出现的次数。或者输出包含某个词的那一行。

sed -n '/165.22.31.3/p' /var/log/secure |wc -l

然后发现其实差不多等价于下面的内容(如果一行一个的话):

grep -c '165.22.31.3' /var/log/secure

统计某天csv文件的记录总数

csv文件每5分钟落一次盘,csv文件不包含header。

tee命令,将明细结果保存在额外的文件中。

ls /etl/dta/20200704/tb_scan_*.csv |xargs -n 1 wc -l | tee /tmp/20200704_check.txt |awk '{sum+=$1} END {print "Sum = ", sum}' 

查看某个文件夹内的文件总大小

ls -l|awk 'BEGIN{sum=0;}{sum+=$5}END{print sum;}'

awk统计

网上找到的各种统计。

1、求和

cat data|awk '{sum+=$1} END {print "Sum = ", sum}'

2、求平均值

cat data|awk '{sum+=$1} END {print "Average = ", sum/NR}'

3、求最大值

cat data|awk 'BEGIN {max = 0} {if ($1>max) max=$1 fi} END {print "Max=", max}'

4、求最小值(min的初始值设置一个超大数即可)

awk 'BEGIN {min = 1999999} {if ($1<min) min=$1 fi} END {print "Min=", min}'

字符长度

# 保存某个字段的长度、跟字段内容
awk -F ',' '{print length($67),$67}'   /path/to/my.csv > /tmp/error.csv
# 找出最长的几位
awk -F ',' '{print length($67),$67}'   /path/to/my.csv|sort -nr  | head ;
# 超过40,则输出该行的内容。
awk -F ',' 'length($67)>40{print $0}'  1.csv 2.csv 3.csv 

字段长度超24

貌似awk比较容易。其他的方式,不太好写正则

awk -F, 'length($2) > 24 || length($3) > 24 ' sd_td_mail_02_202107*
sed -n -r '/\,\w{25,}/p' sd_td_mail_02_202107* |head
grep 

打印具体某一行

# 注意 是两个等号。而不是 =
awk -F ',' 'NR==485383{print $1}'  /path/to/my.csv

sz发送未下载的文件

# 在已下载的机器上生成已下载的文件列表
ls *.flv > download.txt
# 在服务器上生成所有的文件列表
ls *.flv > all.txt
# 重命名已下载的文件
cat all.txt download.txt |sort |uniq -c |sort -n|awk '$1==1{print substr($0,8)}'  |xargs -n 1 -i mv {}  tototo{}
# 下载
sz tototo*.flv

查看某行文件的最大长度分布情况

sort -nr 按数字来排序,逆序。

head -n 1000 |tail 查看1000行左右的内容。替代方法 sed

awk -F ',' '{print length($8),$8}' /etl/dta/20200704/th_gpsdata_20200704144000.csv |sort -nr |head -n 1000 |tail

printf 命令

使用ls printf xargs 三者配合,达到生成特定的片段。

ls *.csv|xargs -n 1 printf "###%s###\n"

说明:printf只接受参数,而不接受标准的输入流。所以使用上面的方式。

导入到pg

使用命令,利用sed的编辑功能,生成特定格式的sql,然后执行导入功能。

# 先进入csv文件夹,然后执行
ls *.csv |sed "s:^:copy sandan_poi from '`pwd`/: "|sed "s:$:' with csv header;: " > /tmp/目标.sql
psql -h localhost -U gpadmin -d develop -f /tmp/目标.sql

ls 绝对路径

上面来源于这个例子

ls  |sed "s:^:`pwd`/: "

备注:由于替换一般使用 /分隔,但是路径中又多包含这个符号,为了避免歧义,使用:分隔,好像|也是可以的。

代码行数统计

感谢通配符号,使得统计代码,非常得方便。

ls *.php */*.php */*/*.php
wc -l  *.php */*.php */*/*.php   # 推荐使用
ls  *.php */*.php */*/*.php  |xargs -n 1 wc -l
cat  *.php */*.php */*/*.php  |wc -l
sed "/^\s*$/d" *.php */*.php */*/*.php  |wc -l   # 剔除空行

#  查找注释并打印输出
sed -n -e '/^\s*\//p;/^\s*\*/p;' Worker.php

# 输出空行、删除注释,再统计
sed -e '/^\s*$/d;/^\s*\//d;/^\s*\*/d;'  *.php */*.php */*/*.php  |wc -l 
# 说明  -e 表达式有3个,以;分隔   /正则/d
# /^\s*$/d;      删除空行, 
# /^\s*\//d;     \/ 转移,表示  / 字符。
# /^\s*\*/d;      \*  转移,表示 *字符 

代码行数统计(脚本)

使用脚本统计

for f in `ls *.php */*.php */*/*.php`;do
count=`sed -e '/^\s*$/d;/^\s*\//d;/^\s*\*/d;' $f|wc -l`;
#echo "$count   $f"
printf "% 4d  %s\n" $count $f
done

字符串排序

因为无法直接在echo中 输出换行符号,故用 sed 来替换成换行符号。

echo -e 参数支持转义符号。

echo 'b c a' |sed 's/\s/\n/g'|sort
echo -e "b\nc\na"|sort

awk拆分

原始的数据结构如下:

大末端超市|98091419,98091419,98091419|app1,app1,app1,app1,app1,app1|market|980090|韵达超市-刘达达

拆分的脚本如下:

# 更新部门信息
echo group_cn,group_en,leader_name,leader_number > import_group.csv
awk -F '|' '{print $1","$4","$6","$5}' basic-data-20210601_sort.csv >> import_group.csv

# 更新人员的对应关系
echo group_en,number > department_number.csv
awk -F '|' '{print $4","$2}'  basic-data-20210601_sort.csv| awk -F ',' '{for (i = 2; i <= NF; i++) print $1","$i}' >> department_number.csv

# 更新关联app
echo group_en,deploy > department_deploy.csv
awk -F '|' '{print $4","$3}'  basic-data-20210601_sort.csv| awk -F ',' '{for (i = 2; i <= NF; i++) print $1","$i}' >> department_deploy.csv

swagger operationId

uniq -c 能查看重复的数字

如果返回的结果太多,还能近一步用 grep -v 来排查(注意正则),或者awk $1 > 1.

grep operationId qingcloud-api.swagger.yaml |sort | uniq -c

# 太多的时候使用
grep operationId qingcloud-api.swagger.yaml |sort | uniq -c |grep '^\s+[^1] '

随机密码

tr是转换工具。下面的亮点在于这种命令格式tr < |,从来没有遇到过这样的管道组合。故记录下来。

tr -dc A-Za-z0-9_ </dev/urandom  | head -c 8 | xargs

批量求md5sum

只求一个文件的md5比较简单,关键是要求批量。原来很多命令,都可以直接执行。

这个时候利用上xargs,比较方便。

# 更简单的方式
ls *.jpg |xargs md5sum   > ../s.txt
# 其实更方便的方式如下(弊端好像也有,文件不能太多太多):
md5sum *.jpg > s2.txt

脚本的方式:

#!/bin/bash

if [ -z $1 ] ;then
    echo "Usage: $0 targetpath,$0 my/path"
    exit
fi

count=$(ls $1|wc -l)
i=1
for file in $(ls $1);do
    echo "$i/$count $file"
    md5sum "$1/$file" >> md5.txt
    i=$[i+1]
done

挑选照片

按名称挑选照片

md5sum *.jpg > ../pic.txt

cat pic.txt |awk -F'*' {'print $2'}|xargs printf "cp vivo_z3/DCIM/Camera/%s select\n" |sh

下载照片

pic文件存的是下载图片的路径的路径。文件按序号存储。还功能指定下载指定序号的图片。

注意47前面加了空格,是表示想搜索除 url外的序号。否则,可能序号、url都搜索。

# 下载全部
cat -n pic.txt |awk '{print "curl -o "$1".png "$2}' |sh

# 下载指定行的图片
cat -n pic.txt |grep " 47" | awk '{print "curl -o "$1".png "$2}' |sh
# 其实指定行,用 sed  更好
cat -n pic.txt |sed -n '47p' | awk '{print "curl -o "$1".png "$2}' |sh

下载m3u

# 下载
sed -n '/^http/p' index.m3u8 |awk '{printf "curl -o %s %s \n",NR,$1 ;}' |sh

# 合并文件 注意,直接cat * > ../down.ts 不行,因为顺序不正确
for i in $(seq 1 $(ls |wc -l));do cat $i  >> down.ts ;done 

其他命令

# 进度
ls |sort -n |tail
# 检查文件  查看最小的文件
ls -Slhr |head
# 指定行的下载格式 注意空格是用来匹配的
sed -n '/^http/p' index.m3u8 |awk '{printf "curl -o %s %s \n",NR,$1 ;}'|grep "o 6 "

统计不包含关键字的文件

查找关键字:AccessControl::className,避免自己每个文件手动打开,检查。故:

其他方法。直接 grep “AccessControl::className” -R . 这个只能查找到有的文件。

for each in `ls `;do grep "AccessControl::className" $each > /dev/null ; if [ $? -ne 0 ]; then echo $each; fi; done

xargs与awk、sed

假设在/md目录下执行操作。

find .  -name '*.md' |xargs  -n 1 -i echo {} 模板
find .  -name '*.md' |awk '{printf "%s 模板\n",$1}'    # 函数的参数之间,用逗号分隔
# sed 略  反正也是修改,替换
# 或者将输出写到文件   然后for循环

有的时候,需要加上 |sh,来直接执行命令。或者保存到文件,先看下输出是什么样子的。

awk  ...  |sh

统计字段

拿到每个csv文件的header,然后是统计有多少个字段。由于使用了*,head中有文件名称。故使用sed,删除指定的行。

以下验证每个字段,都是169个字段。

head -1 /addr7/full_piece/real/202106*/*00.csv |awk -F',' '{print NF}' |sed '/^0$/d;/^169$/d;/^1$/d'

突然发现,也可以sort -u.

head -1 /addr7/full_piece/real/202104*/*00.csv |awk -F',' '{print NF}'|sort -u

排序

文本处理的结果如下:

sz Linux三剑客超全超详情教程(grep、sed、awk入门到精通有这一套足够了) (P1. 01-shell编程-debug).flv
sz Linux三剑客超全超详情教程(grep、sed、awk入门到精通有这一套足够了) (P2. 02-shell编程-总结).flv
sz Linux三剑客超全超详情教程(grep、sed、awk入门到精通有这一套足够了) (P3. 03-三剑客正则-正则什么鬼).flv
sz Linux三剑客超全超详情教程(grep、sed、awk入门到精通有这一套足够了) (P4. 04-三剑客正则-正则vs通配符).flv
sz Linux三剑客超全超详情教程(grep、sed、awk入门到精通有这一套足够了) (P5. 05-三剑客正则-基础正则01).flv

思路:sed -r来开启正则功能,将要匹配的内容,用括号括起来。\1,引用第一个匹配的内容。\0,匹配该行。然后转换成数字来排序。考虑到要处理的内容中有空格,故换成了---分隔。

ls *.flv |sed -r 's#^.*P(.*)\. .*$#\1---\0#' |sort -n |awk -F'---' '{print "sz "$2}' 

突然发现:

因文件名中包含了空格啥的,我用*来匹配。则出现如下方式。直接按数字循环,即可。也不用排序了。

ls *20*.flv
sz *20*.flv

seq 1 20 |awk '{print "ls *"$0"*.flv"}'|sh  -- 尽量避免出现重复的

统计文件个数

# 统计月的文件夹
for each in `seq 1 31`;do ls /addr7/full_piece/real/202105`printf '%02d' $each`/*00.csv|wc -l;done

# 
for each in `seq 1 31`;do 
	path=/addr7/full_piece/real/202105`printf '%02d' $each`/*00.csv ;
	echo $path;  # 没有想到,居然是一批目录,通配符号已经被解释了
	ls $path|wc -l ;
done

csearch提取函数

 csearch "create or replace function" |awk -F ':' '{print $2}' |sort -u |sed 's/^/c:/;s#\#/#g' > functions.txt
for each in `cat functions.txt`; do  sed -n '/create or replace function/,/language/p' $each >> plpgsql.txt ;done
# 每个language 后面增加空行的
sed -i '/language pl/a \\n\n\n'  plpgsql.txt
# 上面的为啥是 \\n  因为\\n输出的是 回车
# 如果是  \n  结果就会输出一个 换行 n 字母。  貌似是吞了一个 \

网络状态统计

time_await较高,然后自己搜到如下命令。

https://zhuanlan.zhihu.com/p/45102654

netstat -an | awk '/^tcp/ {++State[$NF]}END{for(key in State)print key "\t" State[key]}'

转换为csv

原理很简单,就是替换分隔,遇到双引号,转换成两个双引号。(可能因为要转义,显得难懂一些)

$1 = $1 为啥要加这一句,我还不懂,但是两者差别很大。测试过,只要 数字不超过本身的长度即可,超过了,则在末尾多出很多多余的列。如有3列 ,$2 = $2,$3 = $3,也是可以的。

awk 'BEGIN{FS="\t";OFS="\",\""}{gsub(/"/,"\"\""); $1 = $1;printf "\"%s\"\n",$0}'
awk '{a[$1]+=$3}END{for(i in a)print a[i],i}' vm_cpu_cores.txt |sort -n

csv统计之坑

下面命令,csv统计行数有非常大的出入。

。首先uniq -u的含义,并不是去重,而是只打印唯一行,即凡是重复的就不统计。另外,最好不要使用shell来统计行数,因为字段里面可能会有\n等符号。

find . -name "tu*.csv" | xargs cat | cut -d , -f 1 | sort |uniq -u | wc -l

fork炸弹

非常简短的代码,能实现递归的fork,几秒之类,系统就能被资源耗尽,无响应。

冒号即为函数名称。通过管道,再形成调用。

:() { :|:& };:

查看附近几行

查找特定的内容,并想输出附近几行,grep -n (n为数字)非常的好用。

尝试用sed命令,但是seq 1 10|sed -n '/2/~6p'seq 1 10|sed -n '/2/~/3/p',这种波浪号语法不支持,只支持逗号,这种。

grep -3 "string_agg" /yd/td2/runtime/cron/log.txt

扩展

grep -5 'parttern' inputfile //打印匹配行的前后5行
grep -C 5 'parttern' inputfile //打印匹配行的前后5行
grep -A 5 'parttern' inputfile //打印匹配行的后5行
grep -B 5 'parttern' inputfile //打印匹配行的前5行

-- 查找到行好,配合  sed -n '1,2p' 这中语法,来查看细节

统计导入总数

sed 虽然也能匹配到,但是,用grep分来步来做,更直接一些。

cat -n log.txt| grep 打标 |tail -2
# 每行的数据是这样的  COPY 4167873
sed -n '215611,215807p' log.txt |grep COPY |awk 'BEGIN{sum=0}{sum+=$2}END{print sum}'

wget提交form表单

wget --post-data="question=怎么了&user=zhenghongxi"  
wget --post-data=\"question=怎么了\&user=zhenghongxi\"  

代码模块/写文件

遇到动态写shell到文件,有的时候会用cat。下面是另外一种方式,用大花括号包裹起来的,作为一个整体,相当于会运行一个子shell进程,输出的命令写到文件里面。

借鉴。

{
    echo '#!/bin/sh';
    echo 'set -e';
    echo;
    echo 'dirname "$(dirname "$(readlink -f "$(which javac || which java)")")"';
} > test.sh
&& chmod +x test.sh

以前用到的方式:

# 后台运行
{
    # 多个命令
} &

yii2快速导入模块

快速的生成指定模块下的use 函数。

ls */models/*.php |sed 's#/#\\#g;s/.php$/;/g;s/^/use /'


# 查找函数
grep function common/models/DoubleLiving.php 

{}秒用

比如想在特定的路径下搜索代码,非常的方便。

grep 'api/v1/namespaces' */{contro*,model*}/*.php
#或者
grep 'api/v1/namespaces' */{contro,model}*/*.php

或者备份,

cp config.php{,.bak}

统计代码

grep  -oh 'array_\w*' vendor/yiisoft/yii2/*/*.php |sort |uniq
  • -h 隐藏文件名称
  • -o 只显示匹配到的内容

统计当前目录下的文件格式

下面的小括号,可要可不要,结果一样。

(for each in *.*;do echo ${each##*.};done)|sort|uniq -c |sort -rn

# 貌似还有一点问题
(for each in `find . -type f`;do echo ${each##*.};done)|sort|uniq -c |sort -rn

sed !d

反向操作,666

sed -e '/abc/,/efg/!d' 
# 等价于  sed -n '/abc/,/efg/p' file

seq 1 10 |sed -e '/2/,/5/!d'

sed 条件替换

同理,还有条件删除、条件添加,等等。

# /class/ 正则匹配到该条件,才会触发 s 替换
sed -i '/class/s/Demo/ThreadDemo/' snippet/java/ThreadDemo.java

grep如何在多行匹配内容

grep

  • if ‘abc’ and ‘efg’ can be on the same line:

    grep -zl 'abc.*efg' <your list of files>
  • if ‘abc’ and ‘efg’ must be on different lines:

    grep -Pzl '(?s)abc.*\n.*efg' <your list of files>

参数:

  • -P Use perl compatible regular expressions (PCRE).
  • -z Treat the input as a set of lines, each terminated by a zero byte instead of a newline. i.e. grep treats the input as a one big line. Note that if you don’t use -l it will display matches followed by a NUL char, see comments.
  • -l list matching filenames only.
  • (?s) activate PCRE_DOTALL, which means that ‘.’ finds any character or newline.

for in 多行的困扰

空格分隔,在for里面会当成一个整体。故,可以用sed 、tr等命令,进行转换。

# 当成整体,结果只输出一行
for each in '1 2 3 4';do echo $each;done

# 转换多行
echo '1 2 3 4' |tr '[ ]' '\n')

# 再搭配for使用
for each in $(echo '1 2 3 4' |tr '[ ]' '\n');do echo $each;done


mytest='
1
2
3
4
'
# echo $mytest 跟 echo "$mytest"  显示不一样,后者原样输出
# 按下面即可
for each in "$mytest";do echo $each;done

for + 管道

处理多行数据,我一般习惯用 awk,其实,for,也很好。

备注:(done后面不能加分号,为啥?因为,写其他命令的时候,也没有加啊,sort 后面加;肯定也报错),另外,while也是可以的

for each in `ls **/*.jpg`;do echo ${each%/*};done |sort |uniq

while read + 管道

直接将变量读入到each 中,然后进行处理。

seq 100|while read each;do  echo $each ;echo '####';done
# 再接输出
seq 100|while read each;do  echo $each ;echo '####';done| sed 's/#/bb/g'

对比for+管道,whileread更灵活。for貌似必须要放在最开始,参数,必须要先放到in后面的参数里面。

awk分隔文件

参考1 参考2

在数据分析的时候,希望将不同的数据拆分到不同的文件中。故可以使用下面的命令

seq 1 10 |awk '{print $0 >> $1.txt}'

# 字符串处理  注意,字符串拼接,直接使用双引号即可
# 没有right函数,使用 如下计算
fn=substr($1,length($1),1)".txt"

seq 1 100 |awk '{fn=substr($1,length($1),1)".txt";print $0 >> fn}'

文件拆分如下:

貌似效率并不是太高( >> 会频繁的打开、关闭文件吗????)

awk '{fn=substr($4,length($4)-4,4)".csv";print $0 >> fn}'   /etl/*/tb_*.csv

100G文件,大概10分钟

awk去重

貌似也能按某列,去重。

awk '!a[$0]++' seq.txt 

xargs 并发

xargs -P 并发数

bin/sh

好像是在一个容器内看到如下的代码:

/bin/sh -c TERM=xterm-256color; export TERM; [ -x /bin/bash ] && ([ -x /usr/bin/script ] && /usr/bin/script -q -c "/bin/bash" /dev/null || exec /bin/bash) || exec /bin/sh

代码大概的含义是:

/bin/bash如果存在,则优先运行/usr/bin/script,否则运行/bin/bash

不存在时,则运行 /bin/sh。(兜底方案)

疑问:为啥bashsh都是用 exec来运行呢?

json取巧处理

懒得调用其他的脚本语言来处理了,直接使用很长的省略号,来匹配固定字数的字符。比如下面,是为了获取订单号。配合awk取出数字。

curl  -s --location --request GET "$API_HOST/v1/warning_order/list?&limit=&status=1" \
--header "token: $TOKEN" \
--header 'appId: w4vr9fowiwgjwyb8' \
--header 'User-Agent: apifox/1.0.0 (https://www.apifox.cn)' |grep -o '"order_id":".................................'  |awk -F'"'  '{print $4}'  > undo_order_list.txt

如果,上面的步骤,还需要在下一步处理,则可以,导出到文件,或者定义成一个函数。或者,懒的话,直接丢到$()等语法块,还可以配合管道,接上while read each等方式。

也可以使用下面的方式,来匹配整

curl ..... | grep -o '"order_id":"[^"]*'|grep -o [0-9]*

grep直接正则匹配

grep参数-P 使用perl的正则匹配模式。

wget -O- https://zenodo.org/record/5092942 | grep -oP 'https://zenodo.org/record/5092942/files/flightlist_\d+_\d+\.csv\.gz' | xargs wget

ps查看内存占用情况

%cpu进程的cpu占用率%mem进程的内存占用率vsz进程所使用的虚存的大小

Rss进程使用的驻留集大小或者是实际内存的大小

TTY与进程关联的终端(TTY)

STAT检查的状态:进程状态使用字符表示的,如R(运行正在运行或准备运行)、S(休眠睡眠)、I(空闲空闲)、Z(僵死)、D(不可中断的睡眠,通常是I/O))、P(等待交换页)、W(换出,表示当前页面不在内存)、N(低优先级任务))T(终止)、W没有驻留页面)

START(进程启动时间和日期)

TIME;(进程使用的总cpu时间)

ps aux |grep php  |awk '{print $6/1024 "M" "\t" $0}'

显示进程是什么时候启动的,lstart

ps -eo pid,lstart,etime,cmd |grep php

k8s获取集群pod状态

kubectl get pods -A |awk 'NR>1{arr[$4]+=1}END{for(n in arr){printf "%30-s\t%5d\n" ,n,arr[n]}}'

查看网络连接

# 查看网络连接
ss -t4n state time-wait sport = :80

# 使用awk 来统计
ss -t4n state time-wait sport = :80 |awk '{split($4,ip);s[ip[1]]++} END{for (i in s ){ if (s[i]>10)print i,s[i]}}'

# 加入防火墙
echo iptables -I INPUT -s $ip -j DROP

awk多行为单位,统计

使用ansible统计机器的cpu等信息。亮点在于:多行内容,提取感兴趣的内容。


ansible all -m shell -a "cat /proc/cpuinfo|grep proce|wc -l" |tee log_cpu.txt

cat log_cpu.txt  |awk 'NR%2==1{printf $1 "\t"}NR%2==0{printf $0 "\n"}' > cpu.txt
# 查看内存
ansible all -m shell -a "free -h |grep Mem"|awk 'NR%2==1{printf $1 "\t"}NR%2==0{printf $2 "\n"}'

exec

有个很疑惑的问题,为啥要使用 exec这个命令呢? 直接使用vim不就可以了?

exec vim "$@"

sed + grep 只替换指定文件

参考文章

这应该是一种常用的套路。像sed、mv、wc等等命令都可以接n多个文件名。而这些文件名通过计算而来,比较方便。grep命令,-r代表递归查找当前文件夹,而-l参数,只显示文件名,而不显示具体查找出的内容(文件名只会出现一次)。

sed -n "s?](.*|pic|?](http://xxx.clouddn.com|pic|?gp" `grep "|pic|" -rl ./`

js资源版本号替换

主要的难点:搞清楚,sed里面的\d不能用,只能使用[0-9],另外,记得双引号内的变量才会解析。


VERSION=`date '+%Y%m%d%H%M%S'`

sed -i "s/ver=[0-9]*/ver=$VERSION/g"  frontend/web/safe_list/*.html
sed -i "s/ver=[0-9]*/ver=$VERSION/g"  frontend/web/safe_list/html/*.html

找不到指定内容,则输出文件名称

一开始的想法是,先grep后获取文件命令,再对比,那些文件名不在列表中。感觉该思路略麻烦,遂直接采用下面的方式。

for 直接用来遍历文件夹。 grep的输出结果直接丢弃。并根据是否成功,采取进一步的操作。

for each in backend/controllers/*.php;do
    grep 'AccessControl' $each >/dev/null ;
    if [ $? -ne 0 ];then 
        echo $each;
    fi;
done

if-else

属于低配版的If else。当然,如果无论测试结果是否为true都执行,则更改为顺序语句即可。即cmd1;cmd2;cmd3

[ 'yes' == 'yes' ] && echo 1 || echo 2
# 甚至嵌入到其他表达式中。``  或  $( 命令 )
RESULT=$([ 'yes' == 'yes' ] && echo 1 || echo 2)

$CMD

适用于,先展示要执行的命令,后执行。

#先展开,后执行
CMD="echo 1"
$CMD  


# 也可以,但是会作为一个整体,即不能加任何参数
CMD="ls"
# CMD="ls -alh" # 会作为一个整体识别,而出错。
"$CMD"


# 替代方法
CMD="ls -alh"
echo $CMD |sh
echo "$CMD" |sh # 暂未发现差别

gum

[ $(gum choose yes no) == "yes" ] && echo yes || echo no
gum confirm 'continue ,now?'  && echo yes || echo no

# 从指定文件中选择一个执行
find . -type f -name "*.sh" -exec grep "^docker"  {} \; |grep run |sort -u |gum filter

统计每个文件夹下的文件数量

关注点:遍历文件,要防止文件名中有空格,故最好采用 in *.flv这种格式,其返回的字符串,如果有空格,应该会包裹上引号,防止被空格拆分。而 ls后面的,也是一样的,继续包裹保护。

for i in *.flac;do  echo $i    `ls "$i/"*".flac"|wc -l`;done

拷贝目录

find . -type d -exec mkdir -p "/e/教程/"{} \;

# 清理空目录
 find . -type d -empty -exec rmdir {} \;

xargs并发请求示例

参数 -P 指定并发的进程数。

-i 可以使用大括号,来表示要引用到的参数。

-n 表示每次只消耗n个参数。

awk '{print $1}' /var/log/nginx/access.log |sort -u |xargs -n 1 -P 3 -i python ~/ip_location.py {}

awk之执行命令system

使用system函数,来调用系统的命令,从而能调用其他任何的命令。需要注意的是,system("这里面参数不会被解析"),一般紧挨着双引号外面放即可。

顺便讲一下,为啥awk命令参数,都用'{print ...}'这种单引号包裹,这是为了保护里面的$1这些变量,这些变量实际并不需要被外面的bash来带入进去,使用了$变量,而又没有保护的话,那么肯定会报错。

那么,如果我真的需要外面的参数怎么办?放在引号外,即可。它在shell里面,即为字符串的拼接功能。

awk '{print $1}' /var/log/nginx/access.log |sort|uniq -c |sort -rn |awk '{printf $1"\t";system("python3 /home/chaofml/git/snippet/python/ip_location.py "$2)}'

查找md文件中的引入链接

核心点是,开启grep的perl模式。

grep --help|grep regexp
  -E, --extended-regexp     PATTERN is an extended regular expression (ERE)
  -G, --basic-regexp        PATTERN is a basic regular expression (BRE)
  -P, --perl-regexp         PATTERN is a Perl regular expression
  -e, --regexp=PATTERN      use PATTERN for matching
  -w, --word-regexp         force PATTERN to match only whole words
  -x, --line-regexp         force PATTERN to match only whole lines

正则部分讲解

https?://.*?             # 想要匹配的内容
\(https?://.*?\)         # 带上边界条件,但又不是我想要的
\(       \)              # 括号,本身也是正则内的语法,要用到转义
(?<=\()                  # 反向预测 
(?=\))                   # 正向预测
(?<=\()https?://.*?(?=\))# 最终结果

4

find . -maxdepth 4 -name "*.md"  -exec grep -oP '(?<=\()https?://.*?(?=\))' {} \;

split

使用cat * 来合并。


find . -maxdepth 1  -type f  -size +100M -print;

find . -maxdepth 1  -type f  -size +100M -exec split -b 80M {} {}. \;
find . -maxdepth 1  -type f  -size +100M -exec rm -f {} \;

bash *.sh

为啥不能直接使用bash *.sh这种方式呢?这是因为bash test.sh 参数1 参数2,其他都是作为参数传入的。

cat *.sh |bash |tee log.txt

ab压测

注意下,seq命令格式

# seq 起 步长 终
for i in `seq 100 100 2000`;do echo $i; ab -n 100000 -c $i -k http://10.172.41.206:8088/index.html 2>&1 |grep 'Requests per second:'  ;done

watch 监控多条命令

多条指令,整体用引号包裹。注意,如果内部有引号,可能需要转义。

watch "ls page* |wc -l ;ls -lht page*|head -2; ls detail* |wc -l ;ls -lht detail*|head -2;"

scp *.sh


scp `find . -type f -mmin -480 |grep -v '.git'` jobvm:/home/chaofml/git/it-monitor-code


# atime  -n_day   
# mtime  -n_day 

# amin  -n_min

rm find

# 配合 ctrl+a  ctrl+e 也非常方便
# 相当于  find -exec rm {} \;
# 如果有for each in ``;do done,则略微麻烦一些
rm `find . -mmin 60`

对比重复文件

简单的根据文件名,来查找文件是否重复。

for each in *.pdf;do  echo $each;find .. -type f -name "$each" -exec md5sum {} \;;done

mybatis简单分析

最好使用xml来解析,下面只是一种简单的方式。

find ./main/resources/mybatis  -type f  -iname "*.xml"  -exec cat -n {} \; > mybatis-all.xml
grep '<delete ' mybatis-all.xml
grep '<delete ' mybatis-all.xml | wc -l


sed -n '/<delete /,/<\/delete/p;' mybatis-all.xml
sed -n '/<select /,/<\/select/p;' mybatis-all.xml
sed -n '/<update /,/<\/update/p;' mybatis-all.xml


# 循环
for i in insert update select delete;do echo $i;sed -n "/<$i /,/<\/$i/p;" mybatis-all.xml > mybatis-"$i".xml; done
for i in insert update select delete;do echo $i;grep "<$i " mybatis-all.xml | wc -l ;done

# 查看占比
wc -l mybatis-*

# 统计使用的标签
grep -Po '<[a-zA-Z]+' mybatis-all.xml  |sort |uniq -c

spring pom依赖

快速查看项目的依赖

find . -type f -name "pom.xml" -exec cat {} \;|grep 'artifactId'

# 方式2
shopt -s globstar
grep 'artifactId' **/pom.xml