美文网首页程序员首页推荐
从豆瓣Top250中学习shell

从豆瓣Top250中学习shell

作者: 此鱼不得水 | 来源:发表于2018-02-28 15:00 被阅读84次

个人作为一个电影爱好者,豆瓣Top250基本上浏览一遍,最近正好在重新学习shell脚本,所以想着做一个简单的爬虫将豆瓣Top250的内容爬下来然后将内容简单汇总一下。然后通过这个简单的脚本开始入手,通过实践来学习shell命令。

#!/bin/bash

page_begin_index=0
file_begin_index=1
echo "donwload begin..."
while [ $page_begin_index -lt 250 ] ; do
  curl -o /tmp/douban_$file_begin_index.html -s https://movie.douban.com/top250?start=$page_begin_index&filter=
  echo "success to save file [douban_$file_begin_index.html]"
  let page_begin_index+=25
  let file_begin_index+=1
done
echo "success to save all douban top250 file"
let file_begin_index--

sleep 5
cd /tmp

for index in `seq $file_begin_index`
do
  tmp_file_name=tmp_to_delete_$index
  cat douban_$index.html | tr '\n' ' ' | egrep -o '<div class="pic">.*?</div>' >> $tmp_file_name
  while read line
  do
    num=`echo $line | egrep -o '<em class="">\d+</em>' | sed 's/<em class="">\([0-9]\{1,3\}\)<\/em>/\1/g'`
    content=`echo $line | egrep -o '<img width.*?>' | sed 's/<img width="100" alt="\(.*\)" src=.*/\1/g'`
    full_content="$num:$content"
    echo $full_content>>result_file
  done < $tmp_file_name
  rm $tmp_file_name
  rm douban_$index.html
done

echo "success to filter douban top250"

逻辑解释:首先通过模拟http请求叫top250的html下载到本地并且按照一定格式存储起来,将所有下载后的文件删除换行符,遍历下载文件并且找出标示电影排序序号和电影名字的字段,通过正则提取出来然后统一写入目标文件。

下面根据代码的逻辑从上到下开始解读并且扩展相关的命令。

定义变量

page_begin_index=0
file_begin_index=1
像这样的定义非常简单,因为shell不是强类型的变成语言,所以不需要像Java一样声明变量,变量声明之后可以在后面的脚本中以“$var_name”或者${var_name}的形式来使用变量。不过我一般为了简单都是直接使用$var_name的形式。
shell中经常使用‘’和“”,在定义变量的时候也可以使用两种引号,他们的区别是:通过单引号定义的变量,其定义的字面内容是什么,显示就是什么;双引号定义的变量则会进一笔解析然后输出,而不是直接输出变量。可以使用以下的简单脚本来模拟一下:

var=test;echo '$var';echo "$var"

shell中也有数组的概念,声明数组的方式如下:
arr_num=(1 2 3 4 5)
arr_str=('asd' 'fds' 'dfg')
需要注意的是数组之间以空格为分隔符,如果以,为分隔符的话也不会提示错误,只不过在用的时候会发现整个数组其实只有一个元素。

数组中引用元素的方式是:${arr_number[1]}.
数组中的所有内容:${arr_num[@]}
数组长度:${#arr_num[@]}
数组赋值:arr_num[1]=100; arr_num[100]=222

在数组的赋值过程中并不会出现越界的情况,例如上面的例子,我直接将index=100的数组项赋值为222。
注意:一个经常出现的错误就是定义变量的时候多加了空格符号,在很多编程语言中定义变量前后习惯性的添加空格符号,这么做也是为了代码的观赏性。然而在shell中这么做却是错误的,直接就会导致变量的定义失败,所以在shell中一定不要随便加空格!!

echo

echo命令在shell中用的十分广泛,中文的解释叫做“回响”,说白了就是将变量打印到终端。我们经常在shell中打印出一些提示性的信息,使用的就是这个命令,echo的话要注意的主要有这两个地方:

  • echo命令默认是带换行符号的,即会在输出内容的末尾追加上一个换行符号
  • echo -e 识别一些以\开头的一些特殊字符

循环(while,for)&程序控制(if-else)

  • while循环
    while [ 条件 ]
    do
      逻辑语句
    done
    需要注意的是条件于左右方括号之间都有一个空格,这个空格必不可少!当然while还有很多种其他的写法,只不过我自己使用的多的是这一种写法,具体大家喜欢哪种写法都因人而异,下面给出一个简单的while循环的例子:
num=1
while [ $num -lt 10 ]
do
    echo "$num"
    let num++
done

本例就是简单打印一下数字,但是要注意的地方就是while循环条件的书写,条件的前后都有一个空格,并且中间的<写成了-lt(less than)代表小于,其中的比较符号都是英文单词的单词第一个字母的组合,-lte小于等于,-gt大于,-gte大于等于等,-ne不等于。
上面的写法是比较标准的写法,但是很多人习惯这么写:

num=1
while [ $num -lt 10 ]; do
    echo "$num"
    let num++
done

我严重怀疑这种写法是java程序员创造出来的,因为在java中习惯的写法是这样子:

while (条件) {
    语句
}

所以可以把上面的do理解为左括号,done理解为右括号。如果是这样子的话就有人会问那;有什么作用呢?其实“;”的作用就是将多条语句放到一行执行,跟java的“;”差不多的,我们有时候为了将多行代码放到一行上写经常会在一条语句的末尾加上“;”。

  • until循环
    其实util循环就是我们通俗理解的do。。while循环,只不过换了个名字而已,下面看一下until的用法:
until [ 条件 ]; do
  语句
done

看起来跟while十分相像,只不过until会先执行一遍循环体而while循环会先判断一下条件。其他的语法都一样,具体参考上面while的语法就可以。

  • for循环
    for循环的写法比较多,但是每种写法都有一种使用场景,所以下面会涉及多种for循环的写法:
#语法一  
for 变量 in 值1 值2 值3..  ; do
  程序块儿  
done 

#语法二
for 变量 `shell命令` ; do
  程序块儿
done

#语法三
for ((初始变量;循环控制;循环结束条件)); do
  程序块儿
done

语法一适用于遍历列表已知的情况下,语法二在shell中经常互现,通过``声明命令,然后将结果传入for循环中进行遍历,语法三就是我们最熟悉的正常for循环写法了,只不过在循环条件外部的括号多了一对。下面给出三种语法的例子:

for index in 1 2 3 4 5;do
  echo $index
done

for single in `ls -a`;do
  echo $single
done

for ((i=1;i<=10;i++));do
  echo $i
done
  • if-else
    shell的if-else与其他的编程语言中的if-else有点差别,主要是写法上:
if [ 条件 ]; then
    程序块儿
elif [ 条件 ]; then
    程序块儿
else
    程序块儿
fi

shell简化了else-if语句为elif,非常明显的程序员式“偷懒”,并且在if语句的结束部分要添加最后的fi(if的反转),条件部分与前后方括号之间还是分别保持着一个空格,这个空格必不可少!下面主要说一下if的条件判断。
if的条件包括上面提到的while条件,即使用-eq,-lt等的语句,但是又不仅仅限于此,if的支持更加丰富,if的判断条件针对文件有非常丰富的参数,例如判断一个文件是否是目录可以使用[ -d file ],判断一个文件是用户可以写的则可以使用[ -w file]等等。

  • -r file 用户可读
  • -w file 用户可写
  • -x file 用户可执行
  • -d file 文件为目录
  • -f file 文件为正规文件
  • -e file 文件存在
test_file=/tmp/testfile
if [ -e $test_file ]; then
       echo "testfile is existed"
else
       touch $test_file
       echo "cerate file $test_file"
fi
if [ 1 -gt 2 ];then
       echo "1 is greatter than 2"
else
       echo "2 is greatter than 1"
fi

curl

shell中用来发送http请求的命令通常是wget和curl,wget自己平时用的并不多,所以不多介绍,感兴趣的话自己百度一下区别就可以。
curl主要用来模拟http请求(当然也可以模拟其他协议发送请求),curl的命令一般格式为:curl -参数 url地址

curl www.baidu.com 返回百度主页的html文件(自行尝试)

正常情况下如果我们不指定请求参数的话会默认发送GET请求,如果我们想要发送POST请求的话需要制定参数-d,例如:

curl -d "param1=value1&param2=value2" "http://www.baidu.com"

curl -l "www.baidu.com" 只显示HTTP响应头信息
curl -s "www.baidu.com" 减少输出信息 我在上面的代码中为了防止curl的默认输出信息比较多导致打印内容比较多所以就用了这个参数

curl -o file "www.baidu.com" 将百度主页的html文件下载到本地的file文件中去

对于curl其实这里有个不解的东西,因为在之前的脚本编写中发现curl -o命令竟然在后面代码运行的时候依然没有执行完,所以说curl命令应该不是同步单线程执行的,明显是没有阻塞后面代码的执行,所以为了保证curl下载完成后再执行后面的读数据逻辑,我在后面的代码中sleep了5秒钟。

运算

expr 是一种计算工具,能够完成表达式的求值计算(只能计算整数)。用法如下:

four=`expr 2 + 2`
ten=`expr 2 * 5`
zero=`expr 1 % 1`

expr的命令必须在``中引用,而且逻辑运算符前后必须有空格,这一点类似于条件控制前后的空格,如果没有空格的话expr会当作字符串直接拼接.但是expr命令对于自增自剪等操作的支持却比较有限,因此便引出了let命令(但是let也有弊端,只能处理整数运算)。

num=1
let num++
let $num=$num+1
echo $num
let num+=10
echo $num
let num--
echo $num

$[]也可以处理表达式的运算,例如:

num=2
num1=$[num*2]
num2=$[num+1]

cd

cd:change directory 修改当前目录,这个命令是linux和windows下面通用的目录,cd 目标目录 就可以跳转到指定的目录
cd有一些方便的命令如下:
cd 进入用户主目录;
cd ~ 进入用户主目录;
cd - 返回进入此目录之前所在的目录;
cd .. 返回上级目录;

cat

cat主要用来查看文件内容,查看线上日志的时候最常用的命令就是cat了,cat的用法主要是下面两种:

    cat /tmp/test_file
    cat /tmp/test_file /tmp/test_file2 /tmp/test_file3
    cat -n /tmp/test_file

cat命令后可以接多个文件名字,代表依次从上至下查看这些文件的内容,但是不宜接过多的文件,因为肉眼能看到的内容比较有限,所以往往cat命令还会接上其他的命令来处理后续内容;有时候我们需要看到文件的行数,-n参数就可以帮我们输出具体的行数信息。之前在使用过程中有时候我会默认把cat当作echo使用,这种做法是不对的,因为cat后面紧接的是文件,而echo后面紧接的字符内容。

tr

一般我们使用tr(translate)是因为tr可以看作简化版的sed,使用tr一般都是为了删除或者替换字符串。tr的使用非常简单,看例子:

echo 'asd' | tr 'a' 'A' 将a替换成A
echo 'asd' | tr "[a-z]" "[A-Z]" 将小写字母替换为大写
tr '/n' ' ' < /tmp/test_file 将test_file中所有的换行符号删除

tr命令有两种比较常用的参数,一个是-d 代表删除,另外一个是-s 代表去除相邻的重复字符,只保留一个,例子如下:

echo 'asd' | tr -d 'a' 删除字符a
echo 'aaasd' | tr -s 'a' 将三个连续的a替换为1个啊,即输出结果为 'asd'
tr -s '/n' < /tmp/test_file 去除test_file中多余的空行
echo 'aaasd' | tr -s '[a-z]' 去除所有的连续小写字母

egrep

egrep是一款linux下的正则表达式搜索命令,在shell中用的比较广泛,他会把匹配的文本打印出来,通常的使用方法是:egrep [参数] [pattern] [目标文件],通过下面的例子来看一下egrep的通常用法:

egrep 'a' "/tmp/test_file" 在test_file下寻找带有字符a的行
egrep '^a' "/tmp/test_file" 在test_file下寻找以a为行开头的行
cat 'asd' | egrep 'a' 在'asd'中寻找字符a

egrep的用法最普遍的就是上面两种,一种是通过管道接受数据,另一种是直接读取文件。egrep可以说是shell中用的最高频率的命令之一,他的强大主要体现在正则的支持上,虽然egrep对于正则的支持并不是很完善,但是对于很大一部分情况都可以解决,因为我个人对于正则表达式比较喜欢,所以这里就介绍的稍微详细些了。

一些字符在正则表达式有特殊的意义,例如+,*,{},() 所以在正则表达式中如果要使用他们的话需要进行转义,转义的做法就是在这些符号前面加上\
1 字母的原样还是按照以前的意思解释,例如 a就是要找a,1就是找1
2 .匹配除了换行意外的所有字符 cat /tmp/test_file | egrep '.' 其实就是查看file中的所有内容
3 []匹配方括号中的任何一个元素 cat /tmp/test_file | egrep '[asd]' 匹配asd中任何一个字母;
- 3.1 如果想匹配一个否定集的话可以这么做: cat /tmp/test_file | egrep '[^asd]' 匹配所有除了asd三个字母的字母
- 3.2 cat /tmp/test_file | egrep [a-z] 匹配所有的小写字母
- 3.3 cat /tmp/test_file | egrep [A-Z] 匹配所有的大写字母
4 ?代表匹配0次或者1次 cat /tmp/test_file | egrep 'aa\?' 代表匹配1个或者两个a,即可以匹配到'a'和'aa'
5 +代表至少1次 cat /tmp/test_file | egrep 'aa\+' 代表匹配至少两个a,即可以匹配'aa','aaaa'。。。。
6 * 代表N次 cat /tmp/test_file | egrep 'aa\*' 代表匹配至少一个a,可以理解为*包含了?和+
7 {}可以匹配指定次数  cat /tmp/test_file | egrep 'a\{1,5\}' 匹配1-5个连续的a
- 7.1 cat /tmp/test_file | egrep 'a\{1\}' 匹配1个a
- 7.2 cat /tmp/test_file | egrep 'a\{1,\}' 匹配至少1个a
- 7.3 cat /tmp/test_file | egrep 'a\{1,5\}' 匹配1到5个a
8 ^ 这个符号有两个意思,正常情况下代表行开头,它并不会匹配具体的字符,只会匹配具体的位置;如果这个字符出现在[]中的话代表取反,如3.1 中介绍的那样。
9 $代表行结束符号,也是只会匹配具体的位置而不会匹配具体的文本。cat /tmp/test_file | egrep 'end$' 匹配以end结尾的行
10 () 反向引用 cat /tmp/test_file | egrep '\(asd\)\1'打标匹配两个连续的asd,“\1”代表前面第一个括号中匹配的内容,在本例中的含义就是“asd”三个字母

这些都是正则表达式的基本内容,简单的需求应该可以满足了。grep还有一些比较重要的参数,这里也稍微普及一下:
cat /tmp/test_file | egrep -i 'a' 忽略大小写的匹配a和A
cat /tmp/test_file | egrep -i 'a' --color=auto 将匹配的文本以其他颜色打印出来,方便肉眼观察
cat cat /tmp/test_file | egrep -o 'a' 只输出匹配的内容,并不会输出匹配的整行文本

管道

命令1 | 命令2 | 命令3

管道的用法就是将管道前的命令的输出结果当作管道后的命令的输入内容,使用管道非常方便,可以避免很多中间变量的生命,可以看到在本文开头介绍的脚本中也使用了很多管道内容

> 和 >>

通常我们创建文件的时候会使用touch命令,创建文件夹会使用mkdir命令,但是为了更加简便我们也会使用>直接创建文件,例如:
echo "try to create file" > test_file 这个命令会将"try to create file"写入test_file文件中,如果这个文件已经存在的话会将原来的内容删除,将"try to create file"写入这个文件中,如果不存在的话就是直接创建一个文件然后写入内容。因为>会删除以前的内容,所以在使用的时候需要小心,小心覆盖掉以前的内容。那么如果我们只是想追加新的内容到原来的文件中要怎么操作呢?

那就是使用>>,这个命令会在原文件中追加内容,如果原文件不存在的话也同样会创建文件并且完成内容输出。需要注意的一点是该命令完成后还会在文本的末尾加上一个换行符。
echo "try to create file" >> test_file

按行读取文件

方式1:
while read line
do
    echo $line
done < test_read.log

方式2:
cat test_read.log | while read line
do
    echo $line
done

这个方式个人感觉没什么好讲的,就是whlie循环然后读文件,写法也比较固定。但是推荐的是方式1,因为方式2会比方式1慢很多。至于慢的原因我这边并不清楚,只是自己在实践过程中发现第二种方式会非常慢,因此推荐方式1

rm

rm(remove)命令用于删除文件,删除文件的话一定要有权限。用法如下:

rm /tmp/test_file 删除单个文件
rm -r /tmp/test_log_dir 删除文件夹,并且循环遍历删除文件夹中的所有内容
rm /tmp/*.log 删除所有以log为结尾的文件

一个很好玩的命令就是'sudo rm -rf /' 听说这个命令运行后会在自己本机出现非常神奇的现象,具体是什么现象呢?自己试试吧(纯碎是个玩笑)~~

本文只是以一个脚本为切入口,简单的介绍了看懂这个脚本需要掌握的内容,对于每个命令的介绍都比较简单,提示提供入门级的认识。如果大家需要进一笔了解这些命令的话可以自行在察阅相关资料。

最后给出上面的shell脚本的结果:

1:肖申克的救赎

2:霸王别姬

3:这个杀手不太冷

4:阿甘正传

5:美丽人生

6:千与千寻

........

相关文章

网友评论

    本文标题:从豆瓣Top250中学习shell

    本文链接:https://www.haomeiwen.com/subject/xcabxftx.html