记录代码改进
记录代码改进
这个文件夹记录代码改进的记录,写代码过程中的思考过程,如何设计、解决需要的问题。或者优化的bug。
这个文件夹记录代码改进的记录,写代码过程中的思考过程,如何设计、解决需要的问题。或者优化的bug。
拉取镜像
javac
以安装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
使用docker非常方便建立临时的开发环境。如下命令,利用-v 将当前开发目录映射到 容器中,然后执行文件。
sudo docker run -it --rm -v ~/go:/go python:3.6 python /go/a.py
直接编译~/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安装命令如下:
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
使用一键脚本安装
wget -qO- https://get.docker.com | sh
在centos服务器上,好像必须要开启服务,ubuntu好像不需要
service docker start
如果只安装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代替. 。
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链接两个容器。
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
每种编程语言都有各自的包管理工具。比如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/
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 config -g repo.packagist composer https://packagist.phpcomposer.com
局部方式(需要进入到项目的根目录,即与composer.json同目录):
composer config repo.packagist composer https://packagist.phpcomposer.com
解除镜像:
composer config -g –unset repos.packagist
参考文章
核心就是,将自定义的包放到git上,然后在composer.json中添加依赖。如下测试,再执行composer update:
"require": {
"pack/test":"dev-master"
},
"repositories":[
{
"type":"git",
"url":"https://github.com/BrucelLi/pack-test.git"
}
]
示例:见com.zip代码例子。
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是比较好的。之前也用过了,但是又踩到坑了,记录一下
。假设文件总共这6行。因为缺少php的结束标记,所以会在最后一行,加上?>结束标记,这样EOD;就变成了EOD;?>这样的标记,
反正会报错。这个时候,需要在末尾再填加一个新行,即可。
注意:
<?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
$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 );
$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;
}
// 设置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 );
在Dockerfile里面RUN命令,那么复杂,为什么很多人都没有将其专门放到一个build.sh脚本中,然后执行呢?而都是用 \ &&来续行?原因是什么?访问不到环境变量?构建参数?
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
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脚本(可能在镜像中已经存在),
思考:也可以不用数组?两种形式实际有如何区别?
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命令会继承或覆盖吗?
其他:
php-apm插件
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文件。
如上文例子,打包指令大、小写不分区。默认全部大写,以示区分。可以使用#号进行注释。#注释,必须顶行写,否则会认为命令内容。
指定基础源。
指定卷标。参见 https://www.jianshu.com/p/ef0f24fd0674
在Dockerfile中,我们也可以使用VOLUME指令来申明contaienr中的某个目录需要映射到某个volume:
VOLUME /foo
这表示,在docker运行时,docker会创建一个匿名的volume,并将此volume绑定到container的/foo目录中,如果container的/foo目录下已经有内容,则会将内容拷贝的volume中。也即,Dockerfile中的VOLUME /foo与docker run -v /foo alpine的效果一样。
Dockerfile中的VOLUME使每次运行一个新的container时,都会为其自动创建一个匿名的volume,如果需要在不同container之间共享数据,那么我们依然需要通过docker run -it -v my-volume:/foo的方式将/foo中数据存放于指定的my-volume中。
如下,一个报错提醒。
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解压而已。
如果是普通的文件,则相当于,直接拷贝到后者。
终上,文件夹内,看到什么,就是什么。
报错如下:
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/ .
跟ENTERPOINT命令相似,放在文件的最后一行(多个CMD只有最后一个生效),主要用来指定,容器启动后执行的命令。此命令,相当于docker run -d image:tag [命令] 后的命令。
为镜像添加触发器。触发器可以是任何指令。如父镜像中定义了ONBUILD触发器,在子镜像的构建中会执行,但是在孙子镜像中不会执行。
ONBUILD ADD . /var/www/
可以使用docker inspect 命令来查看详情。
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 .
注意,构建时,自定义的参数好像显示不出来,依然使用的是${变量命}的形式。但是,构建过程中,可以看到新建的目录等,确实有变化。重要:容易被缓存。
docker在1.9之后的dockerfile中新增关键字STOPSIGNAL。在github,官网,nginx的Dockerfile中见到的。
默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过–stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s 。
具体参见:地址
以下示例包含一个健康检查。另外,还有多阶段构建。
(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 mynewuser
WORKDIR /home/mynewuser
跟docker run 中 -w的参数作用相同。
参见HEALTHCHECK中的示例。
构建镜像有两种方式。1、进入容器,更改容器后,commit容器的变化。2、跟据Dockerfile来进行打包。
Dockerfile方式构建,基于DSL(领域特定语言)的描述打包。构建过程,实际上是运行镜像,有部分指令是在构建过程中的容器中执行,如RUN ,有些可能是在容器启动有执行,如CMD、ENTERPOINT等。而ADD、COPY能将容器外部的文件添加到容器内部。
构建镜像过程中,要区分真机环境跟镜像运行环境。如RUN 后的指令,实际上操作的都是容器内的文件、环境。
文件目录为Dockerfile所在目录
shell是个很强大的工具,界面操作,用其来简单,但是想做到自动化,却有点困难。而且,会将限制用户的输入,这是好处,也是坏出。增加了限制,可以保证执行的有效性,但也限制了功能。
开发界面,也需要一定的成本。而直接开发出不带界面的,可能更容易一些。命令行直接再用强大的管道符号、重定向符号,能完成非常复杂的功能。
另外,*匹配也非常的强大,觉得这个功能,是能是bash解释器的功能。有了它,我们甚至省了循环。而且还有匹配(搜索)特定位置的文件的功能。
还有就是{}序列功能。
脚本语法检测工具,方便测试出脚本中存在的错误。
apt-get install shellcheck
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 + 左右箭头 单词移动
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
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文件每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;}'
网上找到的各种统计。
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
貌似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
# 在已下载的机器上生成已下载的文件列表
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
使用ls printf xargs 三者配合,达到生成特定的片段。
ls *.csv|xargs -n 1 printf "###%s###\n"
说明:printf只接受参数,而不接受标准的输入流。所以使用上面的方式。
使用命令,利用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 |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
原始的数据结构如下:
大末端超市|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
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
只求一个文件的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
# 下载
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
假设在/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]}'
原理很简单,就是替换分隔,遇到双引号,转换成两个双引号。(可能因为要转义,显得难懂一些)
$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统计行数有非常大的出入。
。首先uniq -u的含义,并不是去重,而是只打印唯一行,即凡是重复的就不统计。另外,最好不要使用shell来统计行数,因为字段里面可能会有\n等符号。
find . -name "tu*.csv" | xargs cat | cut -d , -f 1 | sort |uniq -u | wc -l
非常简短的代码,能实现递归的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 --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
以前用到的方式:
# 后台运行
{
# 多个命令
} &
快速的生成指定模块下的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
下面的小括号,可要可不要,结果一样。
(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
反向操作,666
sed -e '/abc/,/efg/!d'
# 等价于 sed -n '/abc/,/efg/p' file
seq 1 10 |sed -e '/2/,/5/!d'
同理,还有条件删除、条件添加,等等。
# /class/ 正则匹配到该条件,才会触发 s 替换
sed -i '/class/s/Demo/ThreadDemo/' snippet/java/ThreadDemo.java
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里面会当成一个整体。故,可以用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
处理多行数据,我一般习惯用 awk,其实,for,也很好。
备注:(done后面不能加分号,为啥?因为,写其他命令的时候,也没有加啊,sort 后面加;肯定也报错),另外,while也是可以的
for each in `ls **/*.jpg`;do echo ${each%/*};done |sort |uniq
直接将变量读入到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后面的参数里面。
在数据分析的时候,希望将不同的数据拆分到不同的文件中。故可以使用下面的命令
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 '!a[$0]++' seq.txt
xargs -P 并发数
好像是在一个容器内看到如下的代码:
/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。(兜底方案)
疑问:为啥bash、sh都是用 exec来运行呢?
懒得调用其他的脚本语言来处理了,直接使用很长的省略号,来匹配固定字数的字符。比如下面,是为了获取订单号。配合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参数-P 使用perl的正则匹配模式。
wget -O- https://zenodo.org/record/5092942 | grep -oP 'https://zenodo.org/record/5092942/files/flightlist_\d+_\d+\.csv\.gz' | xargs wget
%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
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
使用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这个命令呢? 直接使用vim不就可以了?
exec vim "$@"
参考文章。
这应该是一种常用的套路。像sed、mv、wc等等命令都可以接n多个文件名。而这些文件名通过计算而来,比较方便。grep命令,-r代表递归查找当前文件夹,而-l参数,只显示文件名,而不显示具体查找出的内容(文件名只会出现一次)。
sed -n "s?](.*|pic|?](http://xxx.clouddn.com|pic|?gp" `grep "|pic|" -rl ./`
主要的难点:搞清楚,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。当然,如果无论测试结果是否为true都执行,则更改为顺序语句即可。即cmd1;cmd2;cmd3。
[ 'yes' == 'yes' ] && echo 1 || echo 2
# 甚至嵌入到其他表达式中。`` 或 $( 命令 )
RESULT=$([ 'yes' == 'yes' ] && echo 1 || echo 2)
适用于,先展示要执行的命令,后执行。
#先展开,后执行
CMD="echo 1"
$CMD
# 也可以,但是会作为一个整体,即不能加任何参数
CMD="ls"
# CMD="ls -alh" # 会作为一个整体识别,而出错。
"$CMD"
# 替代方法
CMD="ls -alh"
echo $CMD |sh
echo "$CMD" |sh # 暂未发现差别
[ $(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 {} \;
参数 -P 指定并发的进程数。
-i 可以使用大括号,来表示要引用到的参数。
-n 表示每次只消耗n个参数。
awk '{print $1}' /var/log/nginx/access.log |sort -u |xargs -n 1 -P 3 -i python ~/ip_location.py {}
使用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)}'
核心点是,开启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?://.*?(?=\))' {} \;
使用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 test.sh 参数1 参数2,其他都是作为参数传入的。
cat *.sh |bash |tee log.txt
注意下,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 "ls page* |wc -l ;ls -lht page*|head -2; ls detail* |wc -l ;ls -lht detail*|head -2;"
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
# 配合 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
最好使用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
快速查看项目的依赖
find . -type f -name "pom.xml" -exec cat {} \;|grep 'artifactId'
# 方式2
shopt -s globstar
grep 'artifactId' **/pom.xml