用 Docker Compose 部署 PySpider

PySpider 是一个国人编写的强大的网络爬虫系统并带有强大的 WebUI。采用 Python 语言编写,分布式架构,支持多种数据库后端,强大的 WebUI 支持脚本编辑器,任务监视器,项目管理器以及结果查看器。

上面这段是从「PySpider 中文网」摘录的。总而言之,它就是一个 All-in-one 的爬虫系统,比 Scrapy 强的地方,主要就是上手更容易,打开 web 页面就可以开始写爬虫。但「开包即食」这一点,仅限于 demo 阶段,比如在 http://demo.pyspider.org 试用一下。真正到自己用的时候,还是要考虑一个部署的问题。尤其是要充分利用它的分布式架构的时候,怎么部署更是一个避不过去的问题。

我之前做过一个 side project,功能是从国内各大视频网站抓取动漫新番,如果发现更新,就推送通知到 iOS 客户端,iOS 客户端仅展示订阅的动漫列表,观看还需要跳转到各官方应用去。

做这个的动机是现在各大视频网站的版权竞争,导致要追一季新番,不得不来回切换各个视频网站,我又不想打开一堆通知,让它们给我推垃圾信息。所以就搞一个只推送动漫更新的应用吧。

这个项目爬虫端已经完成,iOS 客户端也提交了审核,然而就是审核没有通过,没过的原因有以下三点:

  • 用了一些版权图片
  • 在 webView 预览动漫的时候,某视频网站乱跳红包
  • 审核人员不看动漫,把鸣人的儿子当成了我山寨的一个动漫形象。

后来发现自己已经有点脱宅了,也就没有再去和审核人员争论了,项目就这么流产,和许多其他 side project 一样。

下面是我在做这个 side project 的时候,部署 PySpider 的方案。

Docker + LeanCloud

现在要部署一个 web 项目,用 Docker 已经是首选了,可以节约不少时间。iOS 后端用现成的 LeanCloud 来做,不用写后端代码,又可以节约不少时间。

要用 LeanCloud,需要引入 LeanCloud 的 SDK,而 binux/pyspider 这个 image 并不包含。所以需要自己 build 一个 image 了。方法就是写一个 Dockerfile,内容如下:

FROM binux/pyspider:latest
MAINTAINER suosuopuo <[email protected]>

# include the LeanCloud SDK
RUN pip install leancloud-sdk

然后执行 docker build -t my/pyspider – < Dockerfile,如果需要用到 LeanCloud SDK,只要用 my/pyspider 代替 binux/pyspider 就好了。

数据库和消息队列

尽管最终结果放在 LeanCloud 上,但 PySpider 各个组件运行还是需要数据库支持的。这部分主要参考这个 Deployment of demo.pyspider.org。数据库是 postgresql,消息队列用 redis。

数据库和消息队列手动用 docker 启动,不用 docker-compose 管理,这和 Deployment of demo.pyspider.org 也是一致的。

启动就两条命令:

docker run --name redis -d -p 6379:6379 redis

docker run --name postgres -v /data/postgres/:/var/lib/postgresql/data -d -e POSTGRES_PASSWORD="" postgres

数据库和用户需要手动创建:

docker exec -it postgres bash

然后输入:

psql -U postgres

CREATE USER myname  WITH PASSWORD ‘mypassword’;

CREATE DATABASE taskdb WITH OWNER= myname LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' ENCODING='UTF8' TEMPLATE=template0;

CREATE DATABASE projectdb WITH OWNER= myname LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' ENCODING='UTF8' TEMPLATE=template0;

CREATE DATABASE resultdb WITH OWNER= myname LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' ENCODING='UTF8' TEMPLATE=template0;

Docker Compose

除了数据库和消息队列,其他组件都用 docker-compose 来管理了,
docker-compose.yml 的内容主要参考这个 Running pyspider with Docker,和这个 Deployment of demo.pyspider.org。

主要是让各个组件连接外部的数据库和消息队列,限制一下内存占用,另外设置一个 WebUI 的用户名和密码。Result worker 要使用之前 build 的 image,因为它需要用到 LeanCloud。

然后 docker-compose up -d 就可以启动各组件了。

phantomjs:
  image: binux/pyspider:latest
  command: phantomjs
  mem_limit: 256m
  restart: always
result:
  image: my/pyspider
  external_links:
    - postgres
    - redis
  volumes:
    - ./share:/opt/share
  working_dir: /opt/share
  command: '--taskdb "sqlalchemy+postgresql+taskdb://username:password@postgres/taskdb"  --projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --resultdb "sqlalchemy+postgresql+resultdb://username:password@postgres/resultdb" --message-queue "redis://redis:6379/1" result_worker --result-cls=resultWorker.VResultWorker'
  mem_limit: 128m
  restart: always
processor:
  image: binux/pyspider:latest
  external_links:
    - postgres
    - redis
  command: '--projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --message-queue "redis://redis:6379/1" processor'
  mem_limit: 128m
  restart: always
fetcher:
  image: binux/pyspider:latest
  external_links:
    - redis
  links:
    - phantomjs
  command : '--message-queue "redis://redis:6379/1" --phantomjs-proxy "phantomjs:80" fetcher --xmlrpc'
  mem_limit: 128m
  restart: always
scheduler:
  image: binux/pyspider:latest
  external_links:
    - postgres
    - redis
  command: '--taskdb "sqlalchemy+postgresql+taskdb://username:password@postgres/taskdb"  --projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --resultdb "sqlalchemy+postgresql+resultdb://username:password@postgres/resultdb" --message-queue "redis://redis:6379/1" scheduler'
  restart: always
webui:
  image: binux/pyspider:latest
  external_links:
    - postgres
    - redis
  links:
    - scheduler
    - phantomjs
  command: '--taskdb "sqlalchemy+postgresql+taskdb://username:password@postgres/taskdb"  --projectdb "sqlalchemy+postgresql+projectdb://username:password@postgres/projectdb" --resultdb "sqlalchemy+postgresql+resultdb://username:password@postgres/resultdb" --message-queue "redis://redis:6379/1" webui --need-auth --username ui_username --password ui_password'
  ports:
    - "5000:5000"

ResultWorker

关于 result worker,官方文档并没有给出 demo,我这里贴一下我的 result worker。首先初始化 LeanCloud SDK,每次抓到数据的时候,去 LeanCloud 查询是不是已经存在同名的动漫(一个动漫可以有多个别名)。如果不存在,就创建这个别名,如果已经存在,就继续执行其他的逻辑(简洁起见,不全部贴出了)。

resultWorker.py 放在和 docker-compose.yml 相同的位置,在启动 result worker 的时候已经通过选项指定自定义的 VResultWorker

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# vim: set et sw=4 ts=4 sts=4 ff=unix fenc=utf8:
# Author: suosuopuo<[email protected]>
#
# Created on 2017-06-15 15:37:46

from pyspider.result import ResultWorker
import leancloud
from datetime import datetime

appKey = ''
appID  = ''

class VResultWorker(ResultWorker):
  def __init__(self, resultdb, inqueue):
    self.resultdb = resultdb
    self.inqueue = inqueue
    self._quit = False

    leancloud.init(appID, appKey)
    leancloud.use_region('CN')

  def on_result(self, task, result):
    if result['type'] == 'anime-update':
      self.handle_anime_update(task, result)

  def handle_anime_update(self, task, result):
    title = result['title']

    try:
      alias_query = leancloud.Query('Alias')
      alias_query.include('anime')
      alias_query.equal_to('title', title)

      alias = alias_query.first()
    except leancloud.LeanCloudError as e:
      if e.code == 101:
        Alias = leancloud.Object.extend('Alias')
        Anime = leancloud.Object.extend('Anime')

        alias = Alias()
        alias.set('title', title)

        anime = Anime()
        anime.set('title', title)
        anime.set('area', result['area'])
        anime.set('cover', result['cover'])
        anime.set('weekday', result['weekday'])
        anime.set('ep_num', 12)
        anime.set('is_finished', result['is_finished'])
        anime.set('verified', False)

        anime.save(fetch_when_save = True)

        alias.set('targetAnime', anime)
        alias.save()
      return

...

还有话说

我是很喜欢这样的「全家桶」式的方案的,可以快速地试验一些想法,但是很遗憾 PySpider 的代码已经很久没有更新了。好在目前为止 PySpider 还足够强大。

我没有解释每一条命令、每一个选项,因为这不是 PySpider 的文档,如果需要更详细的解释,还是需要去挖掘官方文档。

写这篇的时间距离写这个 side project 已经隔了几个月。我也懒到没有去再次验证各个配置和命令,所以「仅供参考」。

另外提醒一下,如果有人也打算用 LeanCloud 做后端,需要注意 LeanCloud 是有访问次数限制的。可以根据抓取的页面特点,用 age, itag等功能减少对 LeanCloud 的访问次数。

不过就算没有访问次数限制,对一个远程服务高频访问,也不是好的策略。

在ubuntu中安装Docker Compose

首先是一个例子,命令:

sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

上面的只是一个例子,需要下载最新的版本,可以去这个网站:

https://github.com/docker/compose/releases

接下来授予权限,命令:

sudo chmod +x /usr/local/bin/docker-compose

最后测试,命令:

$ docker-compose --version
docker-compose version 1.16.1, build 1719ceb

如果出现版本信息,说明安装成功。

以上就是在ubuntu中安装Docker Compose的方法。

logstash笔记(二)——grok之match

官方文档:

https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html

基本语法:

%{SYNTAX:SEMANTIC}

SYNTAX:定义的正则表达式名字(系统插件自带的默认位置:$HOME/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-2.0.2/patterns)

SEMANTIC:匹配结果的标识

grok{
  match=>{
    "message"=>"%{IP:clientip}"
  }
}

输入结果

{
  "message" => "192.168.1.1 abc",
  "@version" => "1",
  "@timestamp" => "2016-03-30T02:15:31.242Z",
  "host" => "master",
  "clientip" => "192.168.1.1"
}

clientip就是semantic

每个%{IP:clientip}表达式只能匹配到message中第一次出现的结果,可用如下方式匹配多个相同类型结果

%{IP:clientip}s+%{IP:clientip1}…,如果SEMANTIC定义的相同名字,结果为数组形式,如:

{
  "message" => "12.12.12.12 32.32.32.32",
  "@version" => "1",
  "@timestamp" => "2016-03-30T02:26:31.077Z",
  "host" => "master",
  "clientip" => [
    [0] "12.12.12.12",
    [1] "32.32.32.32"
  ]
}

自定义grok表达式

语法:(?the pattern here)

eg:

grok{
  match=>{
    "message"=>"%{IP:clientip}s+(?<mypattern>[A-Z]+)"
  }
}

rs:

{
  "message" => "12.12.12.12 ABC",
  "@version" => "1",
  "@timestamp" => "2016-03-30T03:22:04.466Z",
  "host" => "master",
  "clientip" => "12.12.12.12",
  "mypattern" => "ABC"
}

创建自定义grok文件

在/home/hadoop/mylogstash/mypatterns_dir创建文件mypatterns_file,内容如下:

MY_PATTERN [A-Z]+

保存!

修改filter

grok{
  patterns_dir=>["/home/hadoop/mylogstash/mypatterns_dir"]
  match=>{
    "message"=>"%{IP:clientip}s+%{MY_PATTERN:mypattern}"
  }
}

结果同上。

logstash解析naxsi日志的问题

目前在用naxsi防火墙,使用elk来做一个日志分析,遇到问题如下:

naxsi作为waf会产生error日志,目前我打开了NAXSI_EXLOG日志选项,因为这个选项可以看到具体的请求内容。

对于同一个请求,naxsi会产生2行或者3行的日志,格式如下:

2017/10/23 17:45:36 [error] 744#0: *19 NAXSI_EXLOG: ip=192.168.141.232&server=192.168.182.141&uri=/sqli-labs/Less-11/&id=1009&zone=BODY&var_name=passwd&content=admin'%20or%20'1'='1'%20xxxxxxxxxx, client: 192.168.141.232, server: _, request: "POST /sqli-labs/Less-11/ HTTP/1.1", host: "192.168.182.141:8000", referrer: "1.1.1.1"
2017/10/23 17:45:36 [error] 744#0: *19 NAXSI_EXLOG: ip=192.168.141.232&server=192.168.182.141&uri=/sqli-labs/Less-11/&id=1013&zone=BODY&var_name=passwd&content=admin'%20or%20'1'='1'%20xxxxxxxxxx, client: 192.168.141.232, server: _, request: "POST /sqli-labs/Less-11/ HTTP/1.1", host: "192.168.182.141:8000", referrer: "1.1.1.1"
2017/10/23 17:45:36 [error] 744#0: *19 NAXSI_FMT: ip=192.168.141.232&server=192.168.182.141&uri=/sqli-labs/Less-11/&learning=0&vers=0.55.3&total_processed=4&total_blocked=4&block=1&cscore0=$SQL&score0=22&cscore1=$XSS&score1=40&zone0=BODY&id0=1009&var_name0=passwd&zone1=BODY&id1=1013&var_name1=passwd, client: 192.168.141.232, server: _, request: "POST /sqli-labs/Less-11/ HTTP/1.1", host: "192.168.182.141:8000", referrer: "1.1.1.1"

这是同一个请求产生的结果,因为每次请求都会有一个id值在里面,这个是19:

未分类

问题:如何取出NAXSI_EXLOG里面的content,跟NAXSI_FMT里面的结果合并到一起?

我写的logstash和正则如下:

DA1 d{4}/d{2}/d{2}
TM1 d{2}:d{2}:d{2}
LEVEL (w+)
NUM1 d+(?:#0: *)
NUM2 d+
EXLOG NAXSI_EXLOG
FMT NAXSI_FMT
ID1 (d+)
ZONE w+
VAR1  (.*)
CONTENT (.*)
T3 w+
T4 HTTP/1.1", host: "(.*)", referrer: "
HOST (.*)

NAXSI %{DA1:date1}s%{TM1:time}s[%{LEVEL:level}]s%{NUM1:num1}%{NUM2:num2}s(?<logtype>NAXSI_EXLOG):sw+=%{HOST:client_ip}&server=%{HOST:hostname}&uri=%{PROG:filepath}&id=%{ID1:id}&zone=%{ZONE:zone}&var_name=%{VAR1:var}&content=%{CONTENT:content},sclient:s%{HOST:ip3},sserver:s(.*)srequest:s"%{T3:method}s%{HOST:uri}sHTTP/1.1",shost:s"%{HOST:host22}"

NAXSI2 %{DA1:date1}s%{TM1:time}s[%{LEVEL:level}]s%{NUM1:num1}%{NUM2:num2}s(?<logtype>NAXSI_EXLOG):sw+=%{HOST:client_ip}&server=%{HOST:hostname}&uri=%{PROG:filepath}&id=%{ID1:id}&zone=%{ZONE:zone}&var_name=%{VAR1:var}&content=%{CONTENT:content},sclient:s%{HOST:ip3},sserver:s(.*)srequest:s"%{T3:method}s%{HOST:uri}sHTTP/1.1",shost:s"%{HOST:host22}",sreferrer:s"(?<referrer>(.*))

FMT %{DA1:date1}s%{TM1:time}s[%{LEVEL:level}]s%{NUM1:num1}%{NUM2:num2}s(?<logtype>NAXSI_FMT):sip=%{HOST:ip}&server=%{HOST:server}&uri=%{UNIXPATH:uri}&learning=%{HOST:learing}&vers=%{HOST:vers}&total_processed=%{HOST:toal_processed}&total_blocked=%{HOST:blocked}&block=%{HOST:block}&cscore0=%{HOST:attack}&score0=%{HOST:score0}&cscore1=%{HOST:xss}&score1=%{HOST:score}&zone0=%{WORD:args}&id0=%{NUMBER:id}&var_name0=%{HOST:varname},sclient:s%{HOST:ip3},sserver:s(.*)srequest:s"%{T3:method}s%{HOST:uri}sHTTP/1.1",shost:s"%{HOST:host22}

logstash.conf:

input {
 file {
       path => "/usr/local/nginx/logs/naxsi.err"
       type => "naxsi-error"
       start_position => "beginning"
   }
   }
   filter {
    if [type] == "naxsi-error" {
    grok {
        patterns_dir => "/opt/logstash-5.5.1/pattern"
        match => [ "message" , "%{NAXSI2}",
               "message" , "%{NAXSI}",
               "message" , "%{FMT}"
            ]

    }
    # aggregate {
    #   task_id => "%{num2}"
    #       code => "map['sql_duration'] = 0"
    #   end_of_task => true
    #   }

}  }
output {
  if [type] == "naxsi-error" {
    elasticsearch {
       hosts => ["localhost"]
       index => "nxapi"
           document_id => "%{num2}"
        }
     }
}

linux三大利器–grep|sed|awk

  • grep 文本查找
  • sed 行编辑器
  • awk 文本处理工具

grep

grep 比较简单 查找文本离不开正则 具体用法如从简单到复杂如

grep '[1-9]' 文件名 //匹配含有1到9数字
grep '[^1-9]' 文件名 //匹配除了1-9数字的其他字符
grep '^root' 文件名 //^变成头字符 以root开头
grep '^$' 文件名 //头和尾加起来 匹配空行
grep '.' 文件名 //匹配.
grep 'w' 文件名 //等同([a-zA-Z1-9_])
grep 'W' 文件名 //等同([^a-zA-Z1-9_])
grep 'b' 文件名 //表示单词分隔 如 b[a]b 就是a
grep 'sb+' 文件名 //匹配至少出现一次的sb
grep 'sb*' 文件名 //有s或者b都可以
grep '.' 文件名 //匹配任意字符

sed

  • 可用自动处理文件
  • 分析日志文件
  • 修改配置文件

  • sed的处理原则是行处理,而且不改变源文件

sed的格式

sed [options] ‘command’ file(s) //命令行格式
sed 'p' passwd #会打印出两行,因为sed的原理是读入一行,输出一行,此处再加上p命令打印出来的一行,所以最后会打印出两行
sed -n 'p'passwd #加了-n选项之后,只会打印出相关的行,那些不相关的行则不会打印出来

Linux grep和find使用方法和区别(四)

在linux下面工作,有些命令能够大大提高效率。本文就向大家介绍find、grep命令,他哥俩可以算是必会的linux命令,我几乎每天都要用到他们。本文结构如下:

  • find命令
    • find命令的一般形式
    • find命令的常用选项及实例
    • find与xargs
  • grep命令
    • grep命令的一般形式
    • grep正则表达式元字符集(基本集)
    • grep命令的常用选项及实例

一、find命令

find命令是一个无处不在命令,是linux中最有用的命令之一。find命令用于:在一个目录(及子目录)中搜索文件,你可以指定一些匹配条件,如按文件名、文件类型、用户甚至是时间戳查找文件。下面就通过实例来体验下find命令的强大。

1.1 find命令的一般形式

man文档中给出的find命令的一般形式为:

find [-H] [-L] [-P] [-D debugopts] [-Olevel] [path…] [expression]

其实[-H] [-L] [-P] [-D debugopts] [-Olevel]这几个选项并不常用,上面的find命令的常用形式可以简化为:

find [path…] [expression]
  • path:find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录
  • expression:expression可以分为——“-options [-print -exec -ok …]”
  • -options,指定find命令的常用选项,下节详细介绍
  • -print,find命令将匹配的文件输出到标准输出
  • -exec,find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为’command’ { } ;,注意{ }和;之间的空格
    find ./ -size 0 -exec rm {} ; 删除文件大小为零的文件 (还可以以这样做:rm -i find ./ -size 0 或 find ./ -size 0 | xargs rm -f &)
    为了用ls -l命令列出所匹配到的文件,可以把ls -l命令放在find命令的-exec选项中:find . -type f -exec ls -l { } ;
    在/logs目录中查找更改时间在5日以前的文件并删除它们:find /logs -type f -mtime +5 -exec rm { } ;
  • -ok,和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。
    find . -name “*.conf” -mtime +5 -ok rm { } ; 在当前目录中查找所有文件名以.LOG结尾、更改时间在5日以上的文件,并删除它们,只不过在删除之前先给出提示

也有人这样总结find命令的结构:

find start_directory test 
     options 
     criteria_to_match 
     action_to_perform_on_results

1.2 find命令的常用选项及实例

  • -name
    按照文件名查找文件。
    find /dir -name filename 在/dir目录及其子目录下面查找名字为filename的文件
    find . -name “*.c” 在当前目录及其子目录(用“.”表示)中查找任何扩展名为“c”的文件
find /dir -name filename
find . name ".c"
  • -perm
    按照文件权限来查找文件。
    find . -perm 755 –print 在当前目录下查找文件权限位为755的文件,即文件属主可以读、写、执行,其他用户可以读、执行的文件

  • -prune
    使用这一选项可以使find命令不在当前指定的目录中查找,如果同时使用-depth选项,那么-prune将被find命令忽略。
    find /apps -path “/apps/bin” -prune -o –print 在/apps目录下查找文件,但不希望在/apps/bin目录下查找
    find /usr/sam -path “/usr/sam/dir1” -prune -o –print 在/usr/sam目录下查找不在dir1子目录之内的所有文件

  • -user
    按照文件属主来查找文件。
    find ~ -user sam –print 在$HOME目录中查找文件属主为sam的文件

  • -group
    按照文件所属的组来查找文件。
    find /apps -group gem –print 在/apps目录下查找属于gem用户组的文件

  • -mtime -n +n
    按照文件的更改时间来查找文件, – n表示文件更改时间距现在n天以内,+ n表示文件更改时间距现在n天以前。
    find / -mtime -5 –print 在系统根目录下查找更改时间在5日以内的文件
    find /var/adm -mtime +3 –print 在/var/adm目录下查找更改时间在3日以前的文件

  • -nogroup
    查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在。
    find / –nogroup -print

  • -nouser
    查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在。
    find /home -nouser –print

  • -newer file1 ! file2
    查找更改时间比文件file1新但比文件file2旧的文件。

  • -type
    查找某一类型的文件,诸如:

b – 块设备文件。
d – 目录。
c – 字符设备文件。
p – 管道文件。
l – 符号链接文件。
f – 普通文件。

find /etc -type d –print 在/etc目录下查找所有的目录
find . ! -type d –print 在当前目录下查找除目录以外的所有类型的文件
find /etc -type l –print 在/etc目录下查找所有的符号链接文件

  • -size n:[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。
    find . -size +1000000c –print 在当前目录下查找文件长度大于1 M字节的文件
    find /home/apache -size 100c –print 在/home/apache目录下查找文件长度恰好为100字节的文件
    find . -size +10 –print 在当前目录下查找长度超过10块的文件(一块等于512字节)

  • -depth:在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。
    find / -name “CON.FILE” -depth –print 它将首先匹配所有的文件然后再进入子目录中查找

  • -mount:在查找文件时不跨越文件系统mount点。
    find . -name “*.XC” -mount –print 从当前目录开始查找位于本文件系统中文件名以XC结尾的文件(不进入其他文件系统)

  • -follow:如果find命令遇到符号链接文件,就跟踪至链接所指向的文件。

1.3 find与xargs

在使用find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。

find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。

在有些系统中,使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高;

而使用xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。

来看看xargs命令是如何同find命令一起使用的,并给出一些例子。

  • find . -type f -print | xargs file 查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类文件

  • find / -name “core” -print | xargs echo “” >/tmp/core.log 在整个系统中查找内存信息转储文件(core dump) ,然后把结果保存到/tmp/core.log 文件中:

  • find . -type f -print | xargs grep “hostname” 用grep命令在所有的普通文件中搜索hostname这个词

  • find ./ -mtime +3 -print|xargs rm -f –r 删除3天以前的所有东西 (find . -ctime +3 -exec rm -rf {} ;)

  • find ./ -size 0 | xargs rm -f & 删除文件大小为零的文件

find命令配合使用exec和xargs可以使用户对所匹配到的文件执行几乎所有的命令。

二、grep命令

grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

2.1 grep命令的一般选项及实例

grep [OPTIONS] PATTERN [FILE…]
grep [OPTIONS] [-e PATTERN | -f FILE] [FILE…]

grep命令用于搜索由Pattern参数指定的模式,并将每个匹配的行写入标准输出中。这些模式是具有限定的正则表达式,它们使用ed或egrep命令样式。如果在File参数中指定了多个名称,grep命令将显示包含匹配行的文件的名称。对 shell 有特殊含义的字符 ($, *, [, |, ^, (, ), ) 出现在 Pattern参数中时必须带双引号。如果 Pattern参数不是简单字符串,通常必须用单引号将整个模式括起来。在诸如 [a-z], 之类的表达式中,-(减号)cml 可根据当前正在整理的序列来指定一个范围。整理序列可以定义等价的类以供在字符范围中使用。如果未指定任何文件,grep会假定为标准输入。

2.2 grep正则表达式元字符集(基本集)

^ 锚定行的开始 如:’^grep’匹配所有以grep开头的行。

$ 锚定行的结束 如:’grep$’匹配所有以grep结尾的行。

. 匹配一个非换行符的字符 如:’gr.p’匹配gr后接一个任意字符,然后是p。

* 匹配零个或多个先前字符 如:’ grep’匹配所有一个或多个空格后紧跟grep的行。 .一起用代表任意字符。

[] 匹配一个指定范围内的字符,如'[Gg]rep’匹配Grep和grep。

[^] 匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep’匹配不包含A-F和H-Z的一个字母开头,紧跟rep的行。

(..) 标记匹配字符,如:’(love)’,love被标记为1。

< 锚定单词的开始,如:’<grep’匹配包含以grep开头的单词的行。

> 锚定单词的结束,如’grep>’匹配包含以grep结尾的单词的行。

x{m} 连续重复字符x,m次,如:’o{5}’匹配包含连续5个o的行。

x{m,} 连续重复字符x,至少m次,如:’o{5,}’匹配至少连续有5个o的行。

x{m,n} 连续重复字符x,至少m次,不多于n次,如:’o{5,10}’匹配连续5–10个o的行。

w 匹配一个文字和数字字符,也就是[A-Za-z0-9],如:’Gw*p’匹配以G后跟零个或多个文字或数字字符,然后是p。

W w的反置形式,匹配一个非单词字符,如点号句号等。W*则可匹配多个。

b 单词锁定符,如: ‘bgrepb’只匹配grep,即只能是grep这个单词,两边均为空格。

2.3 grep命令的常用选项及实例

-?

同时显示匹配行上下的?行,如:grep -2 pattern filename同时显示匹配行的上下2行。

-b,–byte-offset

打印匹配行前面打印该行所在的块号码。

-c,–count

只打印匹配的行数,不显示匹配的内容。

-f File,–file=File

从文件中提取模板。空文件中包含0个模板,所以什么都不匹配。

-h,–no-filename

当搜索多个文件时,不显示匹配文件名前缀。

-i,–ignore-case

忽略大小写差别。

-q,–quiet

取消显示,只返回退出状态。0则表示找到了匹配的行。

-l,–files-with-matches

打印匹配模板的文件清单。

-L,–files-without-match

打印不匹配模板的文件清单。

-n,–line-number

在匹配的行前面打印行号。

-s,–silent

不显示关于不存在或者无法读取文件的错误信息。

-v,–revert-match

反检索,只显示不匹配的行。

-w,–word-regexp

如果被<和>引用,就把表达式做为一个单词搜索。

-V,–version

显示软件版本信息。

=====

  • ls -l | grep ‘^a’ 通过管道过滤ls -l输出的内容,只显示以a开头的行。

  • grep ‘test’ d* 显示所有以d开头的文件中包含test的行。

  • grep ‘test’ aa bb cc 显示在aa,bb,cc文件中匹配test的行。

  • grep ‘[a-z]’ aa 显示所有包含每个字符串至少有5个连续小写字符的字符串的行。

  • grep ‘w(es)t.’ aa 如果west被匹配,则es就被存储到内存中,并标记为1,然后搜索任意个字符(.),这些字符后面紧跟着另外一个es(),找到就显示该行。如果用egrep或grep -E,就不用””号进行转义,直接写成’w(es)t.*’就可以了。

  • grep -i pattern files :不区分大小写地搜索。默认情况区分大小写

  • grep -l pattern files :只列出匹配的文件名,

  • grep -L pattern files :列出不匹配的文件名,

  • grep -w pattern files :只匹配整个单词,而不是字符串的一部分(如匹配‘magic’,而不是‘magical’),

  • grep -C number pattern files :匹配的上下文分别显示[number]行,

  • grep pattern1 | pattern2 files :显示匹配 pattern1 或 pattern2 的行,

  • grep pattern1 files | grep pattern2 :显示既匹配 pattern1 又匹配 pattern2 的行。

Linux查找含有某字符串的所有文件

如果你想在当前目录下 查找”hello,world!”字符串,可以这样:

grep -rn "hello,world!" *
  • * : 表示当前目录所有文件,也可以是某个文件名
  • -r 是递归查找
  • -n 是显示行号
  • -R 查找所有文件包含子目录
  • -i 忽略大小写

下面是一些有意思的命令行参数:

  • grep -i pattern files :不区分大小写地搜索。默认情况区分大小写,
  • grep -l pattern files :只列出匹配的文件名,
  • grep -L pattern files :列出不匹配的文件名,
  • grep -w pattern files :只匹配整个单词,而不是字符串的一部分(如匹配‘magic’,而不是‘magical’),
  • grep -C number pattern files :匹配的上下文分别显示[number]行,
  • grep pattern1 | pattern2 files :显示匹配 pattern1 或 pattern2 的行,
  • grep pattern1 files | grep pattern2 :显示既匹配 pattern1 又匹配 pattern2 的行。

这里还有些用于搜索的特殊符号:

  • < 和 > 分别标注单词的开始与结尾。

例如:

  • grep man * 会匹配 ‘Batman’、‘manic’、‘man’等,

  • grep ‘<man’ * 匹配‘manic’和‘man’,但不是‘Batman’,

  • grep ‘<man>’ 只匹配‘man’,而不是‘Batman’或‘manic’等其他的字符串。

  • ‘^’:指匹配的字符串在行首,

  • ‘$’:指匹配的字符串在行尾,

Ubuntu16.04安装vsftpd,搭建ftp服务

Ubuntu16为例:

1、有问题时完全卸载vsftpd

sudo apt-get purge vsftpd

2、安装vsftpd

udo apt-get install vsftpd

3、创建ftp用户

方法一:直接在系统设置添加用户

方法二:

创建用户目录 sudo mkdir /home/uftp

创建用户 sudo useradd -d /home/uftp -s /bin/bash uftp //uftp就是用户名称

修改密码 sudo passwd uftp //输入不显示

sudo chown uftp:uftp /home/uftp //修改完成后将目录/home/uftp的所属者和所属组都改为uftp,否则无权上传

4、配置vsftpd.conf,输入错误可以按ctrl加z键退出

sudo vi /etc/vsftpd.conf

编辑vsftpd.conf文件

write_enable=YES
//write_enable=YES前面的#号删除。按x删除,按esc退出编辑,按shift键加:号,输入wq保存或者wq!强制保存。注意为英文半角符号

5、启动停止重启服务

sudo /etc/init.d/vsftpd start //启动
sudo /etc/init.d/vsftpd stop //停止
sudo /etc/init.d/vsftpd restart //重启

Vsftpd使用MySQL验证实验

实验环境:

操作系统:CentOS 6.5 64位

未分类

实验步骤:

第1步:安装vsftpd并测试可用性(在vsftp.xruan.com,192.168.0.12上执行)

[root@CentOS6lsrv02 ~]# yum install -y vsftpd
[root@CentOS6lsrv02 ~]# yum install -y lftp
[root@CentOS6lsrv02 ~]# lftp 192.168.0.12
lftp 192.168.0.12:~>

第2步:安装mysql-server并创建用户和表(在mysql.xruan.com,192.168.0.11上执行)

[root@CentOS6lsrv01 ~]# yum install -y mysql-server &> /dev/null
[root@CentOS6lsrv01 ~]# rpm -q mysql-server
mysql-server-5.1.71-1.el6.x86_64

启动mysqld服务

[root@CentOS6lsrv01 ~]# service mysqld start
Initializing MySQL database:  WARNING: The host 'CentOS6lsrv01' could notbe looked up with resolveip.
This probably means that your libc libraries arenot 100 % compatible
with this binary MySQL version. The MySQLdaemon, mysqld, should work
normally with the exception that host nameresolving will not work.
This means that you should use IP addressesinstead of hostnames
when specifying MySQL privileges !
Installing MySQL system tables...
OK
Filling help tables...
OK

To start mysqld at boot time you have to copy
support-files/mysql.server to the right placefor your system

PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQLroot USER !
To do so, start the server, then issue thefollowing commands:

/usr/bin/mysqladmin -u root password'new-password'
/usr/bin/mysqladmin -u root -h CentOS6lsrv01password 'new-password'

Alternatively you can run:
/usr/bin/mysql_secure_installation

which will also give you the option of removingthe test
databases and anonymous user created bydefault.  This is
strongly recommended for production servers.

See the manual for more instructions.

You can start the MySQL daemon with:
cd /usr ; /usr/bin/mysqld_safe &

You can test the MySQL daemon withmysql-test-run.pl
cd /usr/mysql-test ; perl mysql-test-run.pl

Please report any problems with the/usr/bin/mysqlbug script!

                                                             [ OK  ]
Starting mysqld:                                          [  OK  ]

运行 /usr/bin/mysql_secure_installation 按提示设置好mysql中root用户密码,这里我设置为“xruan”。

创建数据库并授予用户权限

mysql> CREATE DATABASE vsftpd;
Query OK, 1 row affected (0.00 sec)

mysql> GRANT ALL ON vsftpd.* TO vsftpd@'%'IDENTIFIED BY 'passwd';
Query OK, 0 rows affected (0.00 sec)
mysql> use vsftpd
Database changed

创建表

mysql> create table users (
    ->id int AUTO_INCREMENT NOT NULL,
    ->name char(20) binary NOT NULL,
    ->password char(50) binary NOT NULL,
    ->primary key(id)
    ->);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into users(name,password) values('xruan',password('xruan'));    插入1行用户信息
Query OK, 1 row affected (0.00 sec)
mysql> insert into users(name,password) values('redhat',password('redhat'));    插入1行用户信息
Query OK, 1 row affected (0.00 sec)
mysql> FLUSH PRIVILEGES;    刷新权限
Query OK, 0 rows affected (0.00 sec)

第3步:配置vsftpd基于MySQL表的虚拟用户(在vsftp.xruan.com,192.168.0.12上执行)

首先必须完成基于MySQL表的认证,需要通过pam_mysql模块实现,先安装pam_mysql

先安装epel源

到官方网站下载epel源的rpm安装包

https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
[root@CentOS6lsrv02 ~]# rpm -ivh epel-release-latest-6.noarch.rpm
[root@CentOS6lsrv02 ~]# yum install -y pam_mysql &> /dev/null
[root@CentOS6lsrv02 pam.d]# rpm -q pam_mysql
pam_mysql-0.7-0.12.rc1.el6.x86_64

建立pam认证所需要的配置文件(/etc/pam.d/vsftpd.mysql)

[root@CentOS6lsrv02 ~]# vi /etc/pam.d/vsftpd.mysql
auth required /lib64/security/pam_mysql.souser=vsftpd passwd=passwd host=192.168.0.11 db=vsftpd table=users usercolumn=name passwdcolumn=password crypt=2
account required /lib64/security/pam_mysql.souser=vsftpd passwd=passwd host=192.168.0.11 db=vsftpd table=users usercolumn=name passwdcolumn=password crypt=2

创建虚拟用户映射的系统用户

[root@CentOS6lsrv02 ~]# useradd -r -s /sbin/nologin vuser -d /var/ftproot
[root@CentOS6lsrv02 ~]# mkdir /var/ftproot
[root@CentOS6lsrv02 var]# chown vuser:vuser ftproot

编辑vsftpd配置文件 /etc/vsftpd/vsftpd.conf ,按如下设置:

listen=YES
anonymous_enable=YES
local_enable=YES
write_enable=YES
anon_upload_enable=NO
anon_mkdir_write_enable=NO
chroot_local_user=YES
guest_enable=YES
guest_username=vuser
pam_service_name=vsftpd.mysql
user_config_dir=/etc/vsftpd/vusers_config

为每个用户单独提供配置文件

user_config_dir=/etc/vsftpd/vusers_config  #在主配置文件(/etc/vsftpd/vsftpd.conf)中添加这个选项
[root@CentOS6lsrv02 vsftpd]# mkdir/etc/vsftpd/vusers_config
[root@CentOS6lsrv02 vsftpd]# vi vusers_config/xruan      #设置xruan用户有所有权限
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES
[root@CentOS6lsrv02 vsftpd]# vi vusers_config/redhat     #设置redhat用户只有upload权限
anon_upload_enable=YES

重启vsftpd服务

[root@CentOS6lsrv02 vsftpd]# service vsftpd restart
Shutting down vsftpd:                                      [ OK  ]
Starting vsftpd for vsftpd:                                [  OK  ]

第4步:测试

[root@CentOS6lsrv02 vsftpd]# lftp -u xruan 192.168.0.12    <--使用xruan用户登录
Password:
lftp [email protected]:~> lcd /etc 
lcd ok, local cwd=/etc
lftp [email protected]:/> put fstab    <--可以上传文件
1224 bytes transferred
lftp [email protected]:~> rm fstab
rm ok, `fstab' removed   可以删除文件

[root@CentOS6lsrv02 vsftpd]# lftp -u redhat 192.168.0.12       <--使用redhat用户登录
Password:
lftp [email protected]:~> lcd /etc
lcd ok, local cwd=/etc lftp [email protected]:/>put selinux/config    <--可以上传文件
457 bytes transferred
lftp [email protected]:/> rm config
rm: Access failed: 550 Permission denied.(config)     <--不能删除文件

到此,实验成功!!!!!

实验—–实现基于文件验证的vsftpd虚拟用户

准备安装包:yum install vsftpd

启动服务: systemctl start vsftpd

步骤:

一、创建虚拟用户数据库文件

①编辑文本文件

vim /etc/vsftpd/ftpuser.txt   奇数行是用户名,偶数行是密码
    ftpuser1
    123456
    ftpuser2
    123456    

②此文件需要被编码为hash格式

cd /etc/vsftpd/
db_load -T -t hash -f ftpuser.txt ftpuser.db  编码为hash格式
chmod 600 ftpser.db               为了安全修改权限
file ftpuser.db                查看文件格式

未分类

二、创建系统用户和访问FTP目录

①useradd -r -m -d /var/ftproot -s /sbin/nologin ftpuser 即/var/ftproot是根目录

②chmod 555 /var/ftproot/ 根目录上不能有写权限,否则不能登录

③mkdir /var/ftproot/upload 创建子目录,方便用户访问

④setfacl -m u:ftpuser:rwx /var/ftproot/upload 给予系统用户对该目录的rwx权限

三、创建pam配置文件,用来完成用户认证

vim /etc/pam.d/vsftpd.db
    auth required pam_userdb.so db=/etc/vsftpd/ftpuser[.db]  
    account required pam_userdb.so db=/etc/vsftpd/ftpuser[.db]
     后缀db可省略,会自动识别

未分类

四、编辑vsftpd配置文件,指定pam配置文件

vim /etc/vsftpd/vsftpd.conf

添加下面两行

guest_enable=YES
guest_username=ftpuser       映射为系统用户ftpuser

修改下行:

pam_service_name=vsftpd.db     pam配置文件

五、SELinux设置

禁用selinu,或者setenforce 0

六、虚拟用户建立独立的配置文件

说明:vsftpd可以在配置文件目录中为每个用户提供单独的配置文件以定义其ftp服务访问权限,每个虚拟用户的配置文件名同虚拟用户的用户名。配置文件目录可以是任意未使用目录,只需要在vsftpd.conf指定其路径及名称即可。

①mdkir /etc/vsftpd/ftpuser.d/ 创建配置文件存放的路径

②编辑vsftp配置文件,指明用户配置文件路径:

vim /etc/vsftpd/vsftpd.conf
    user_config_dir=/etc/vsftpd/ftpuser.d/

③创建用户ftpuser1自已的配置文件,登陆目录为默认目录

(/var/ftproot/)
  vim /etc/vsftpd/ftpuser.d/ftpuser1     文件名同用户名
    anon_upload_enable=YES        支持上传
    anon_mkdir_write_enable=YES       支持创建文件夹

实现效果:wang用户连接ftp登陆之后,进入upload目录可以上传文件和创建文件夹了

④创建用户ftpuser2的配置文件,实现登陆目录改变至指定目录

mkdir /var/ftproot2;chmod 555 /var/ftproot2   作为ftpuser2的登陆目录
 vim /etc/vsftpd/ftpuser.d/ftpuser2 
    local_root=/var/ftproot2       登录目录改变至指定的目录

七、重启vsftpd服务

systemctl restart vsftpd

八、客户端测试:需要事先安装ftp客户端工具(yum install ftp)

①ftp 192.168.199.140(vsftpd服务器ip)

使用ftpuser1登陆,可以使用put上传文件并且可以创建文件夹。效果如下:

未分类

②ftp 192.168.199.140

使用ftpuser2登陆,使用ls看不到任何文件,说明已经更改登陆目录,效果如下:

未分类