精华内容
下载资源
问答
  • 哪项不是文本格式化内容
    千次阅读
    2020-10-23 09:12:16

    所有类 Unix 的操作系统都非常依赖于被用于几种数据类型存储的文本文件。所以这很有道理, 有许多用于处理文本的工具。在这一章中,我们将看一些被用来“切割”文本的程序。在下一章中, 我们将查看更多的文本处理程序,但主要集中于文本格式化输出程序和其它一些人们需要的工具。

    这一章会重新拜访一些老朋友,并且会给我们介绍一些新朋友:

    • cat – 连接文件并且打印到标准输出
    • sort – 给文本行排序
    • uniq – 报告或者省略重复行
    • cut – 从每行中删除文本区域
    • paste – 合并文件文本行
    • join – 基于某个共享字段来联合两个文件的文本行
    • comm – 逐行比较两个有序的文件
    • diff – 逐行比较文件
    • patch – 给原始文件打补丁
    • tr – 翻译或删除字符
    • sed – 用于筛选和转换文本的流编辑器
    • aspell – 交互式拼写检查器

    文本应用程序

    到目前为止,我们已经知道了一对文本编辑器(nano 和 vim),看过一堆配置文件,并且目睹了 许多命令的输出都是文本格式。但是文本还被用来做什么? 它可以做很多事情。

    文档

    许多人使用纯文本格式来编写文档。虽然很容易看到一个小的文本文件对于保存简单的笔记会 很有帮助,但是也有可能用文本格式来编写大的文档。一个流行的方法是先用文本格式来编写一个 大的文档,然后使用一种标记语言来描述已完成文档的格式。许多科学论文就是用这种方法编写的, 因为基于 Unix 的文本处理系统位于支持技术学科作家所需要的高级排版布局的一流系统之列。

    网页

    世界上最流行的电子文档类型可能就是网页了。网页是文本文档,它们使用 HTML(超文本标记语言)或者是 XML (可扩展的标记语言)作为标记语言来描述文档的可视格式。

    电子邮件

    从本质上来说,email 是一个基于文本的媒介。为了传输,甚至非文本的附件也被转换成文本表示形式。 我们能看到这些,通过下载一个 email 信息,然后用 less 来浏览它。我们将会看到这条信息开始于一个标题, 其描述了信息的来源以及在传输过程中它接受到的处理,然后是信息的正文内容。

    打印输出

    在类 Unix 的系统中,输出会以纯文本格式发送到打印机,或者如果页面包含图形,其会被转换成 一种文本格式的页面描述语言,以 PostScript 著称,然后再被发送给一款能产生图形点阵的程序, 最后被打印出来。

    程序源码

    在类 Unix 系统中会发现许多命令行程序被用来支持系统管理和软件开发,并且文本处理程序也不例外。 许多文本处理程序被设计用来解决软件开发问题。文本处理对于软件开发者来言至关重要是因为所有的软件 都起始于文本格式。源代码,程序员实际编写的一部分程序,总是文本格式。

    回顾一些老朋友

    回到第7章(重定向),我们已经知道一些命令除了接受命令行参数之外,还能够接受标准输入。 那时候我们只是简单地介绍了它们,但是现在我们将仔细地看一下它们是怎样被用来执行文本处理的。

    cat

    这个 cat 程序具有许多有趣的选项。其中许多选项用来帮助更好的可视化文本内容。一个例子是-A 选项, 其用来在文本中显示非打印字符。有些时候我们想知道是否控制字符嵌入到了我们的可见文本中。 最常用的控制字符是 tab 字符(而不是空格)和回车字符,在 MS-DOS 风格的文本文件中回车符经常作为 结束符出现。另一种常见情况是文件中包含末尾带有空格的文本行。

    让我们创建一个测试文件,用 cat 程序作为一个简单的文字处理器。为此,我们将键入 cat 命令(随后指定了 用于重定向输出的文件),然后输入我们的文本,最后按下 Enter 键来结束这一行,然后按下组合键 Ctrl-d, 来指示 cat 程序,我们已经到达文件末尾了。在这个例子中,我们文本行的开头和末尾分别键入了一个 tab 字符以及一些空格。

    [me@linuxbox ~]$ cat > foo.txt
        The quick brown fox jumped over the lazy dog.
    [me@linuxbox ~]$
    

    下一步,我们将使用带有-A 选项的 cat 命令来显示这个文本:

    [me@linuxbox ~]$ cat -A foo.txt
    ^IThe quick brown fox jumped over the lazy dog.       $
    [me@linuxbox ~]$
    

    在输出结果中我们看到,这个 tab 字符在我们的文本中由^I 字符来表示。这是一种常见的表示方法,意思是 “Control-I”,结果证明,它和 tab 字符是一样的。我们也看到一个$字符出现在文本行真正的结尾处, 表明我们的文本包含末尾的空格。

    MS-DOS 文本 Vs. Unix 文本

    可能你想用 cat 程序在文本中查看非打印字符的一个原因是发现隐藏的回车符。那么 隐藏的回车符来自于哪里呢?它们来自于 DOS 和 Windows!Unix 和 DOS 在文本文件中定义每行 结束的方式不相同。Unix 通过一个换行符(ASCII 10)来结束一行,然而 MS-DOS 和它的 衍生品使用回车(ASCII 13)和换行字符序列来终止每个文本行。

    有几种方法能够把文件从 DOS 格式转变为 Unix 格式。在许多 Linux 系统中,有两个 程序叫做 dos2unix 和 unix2dos,它们能在两种格式之间转变文本文件。然而,如果你 的系统中没有安装 dos2unix 程序,也不要担心。文件从 DOS 格式转变为 Unix 格式的过程非常 简单;它只简单地涉及到删除违规的回车符。通过随后本章中讨论的一些程序,这个工作很容易 完成。

    cat 程序也包含用来修改文本的选项。最著名的两个选项是-n,其给文本行添加行号和-s, 禁止输出多个空白行。我们这样来说明:

    [me@linuxbox ~]$ cat > foo.txt
    The quick brown fox
    
    jumped over the lazy dog.
    [me@linuxbox ~]$ cat -ns foo.txt
    1   The quick brown fox
    2
    3   jumped over the lazy dog.
    [me@linuxbox ~]$
    

    在这个例子里,我们创建了一个测试文件 foo.txt 的新版本,其包含两行文本,由两个空白行分开。 经由带有-ns 选项的 cat 程序处理之后,多余的空白行被删除,并且对保留的文本行进行编号。 然而这并不是多个进程在操作这个文本,只有一个进程。

    sort

    这个 sort 程序对标准输入的内容,或命令行中指定的一个或多个文件进行排序,然后把排序 结果发送到标准输出。使用与 cat 命令相同的技巧,我们能够演示如何用 sort 程序来处理标准输入:

    [me@linuxbox ~]$ sort > foo.txt
    c
    b
    a
    [me@linuxbox ~]$ cat foo.txt
    a
    b
    c
    

    输入命令之后,我们键入字母“c”,“b”,和“a”,然后再按下 Ctrl-d 组合键来表示文件的结尾。 随后我们查看生成的文件,看到文本行有序地显示。

    因为 sort 程序能接受命令行中的多个文件作为参数,所以有可能把多个文件合并成一个有序的文件。例如, 如果我们有三个文本文件,想要把它们合并为一个有序的文件,我们可以这样做:

    sort file1.txt file2.txt file3.txt > final_sorted_list.txt
    

    sort 程序有几个有趣的选项。这里只是一部分列表:

    表21-1: 常见的 sort 程序选项

    选项长选项描述
    -b--ignore-leading-blanks默认情况下,对整行进行排序,从每行的第一个字符开始。这个选项导致 sort 程序忽略 每行开头的空格,从第一个非空白字符开始排序。
    -f--ignore-case让排序不区分大小写。
    -n--numeric-sort基于字符串的长度来排序。使用此选项允许根据数字值执行排序,而不是字母值。
    -r--reverse按相反顺序排序。结果按照降序排列,而不是升序。
    -k--key=field1[,field2]对从 field1到 field2之间的字符排序,而不是整个文本行。看下面的讨论。
    -m--merge把每个参数看作是一个预先排好序的文件。把多个文件合并成一个排好序的文件,而没有执行额外的排序。
    -o--output=file把排好序的输出结果发送到文件,而不是标准输出。
    -t--field-separator=char定义域分隔字符。默认情况下,域由空格或制表符分隔。

    虽然以上大多数选项的含义是不言自喻的,但是有些也不是。首先,让我们看一下 -n 选项,被用做数值排序。 通过这个选项,有可能基于数值进行排序。我们通过对 du 命令的输出结果排序来说明这个选项,du 命令可以 确定最大的磁盘空间用户。通常,这个 du 命令列出的输出结果按照路径名来排序:

    [me@linuxbox ~]$ du -s /usr/share/\* | head
    252     /usr/share/aclocal
    96      /usr/share/acpi-support
    8       /usr/share/adduser
    196     /usr/share/alacarte
    344     /usr/share/alsa
    8       /usr/share/alsa-base
    12488   /usr/share/anthy
    8       /usr/share/apmd
    21440   /usr/share/app-install
    48      /usr/share/application-registry
    

    在这个例子里面,我们把结果管道到 head 命令,把输出结果限制为前 10 行。我们能够产生一个按数值排序的 列表,来显示 10 个最大的空间消费者:

    [me@linuxbox ~]$ du -s /usr/share/* | sort -nr | head
    509940         /usr/share/locale-langpack
    242660         /usr/share/doc
    197560         /usr/share/fonts
    179144         /usr/share/gnome
    146764         /usr/share/myspell
    144304         /usr/share/gimp
    135880         /usr/share/dict
    76508          /usr/share/icons
    68072          /usr/share/apps
    62844          /usr/share/foomatic
    

    通过使用此 -nr 选项,我们产生了一个反向的数值排序,最大数值排列在第一位。这种排序起作用是 因为数值出现在每行的开头。但是如果我们想要基于文件行中的某个数值排序,又会怎样呢? 例如,命令 ls -l 的输出结果:

    [me@linuxbox ~]$ ls -l /usr/bin | head
    total 152948
    -rwxr-xr-x 1 root   root     34824  2008-04-04  02:42 [
    -rwxr-xr-x 1 root   root    101556  2007-11-27  06:08 a2p
    ...
    

    此刻,忽略 ls 程序能按照文件大小对输出结果进行排序,我们也能够使用 sort 程序来完成此任务:

    [me@linuxbox ~]$ ls -l /usr/bin | sort -nr -k 5 | head
    -rwxr-xr-x 1 root   root   8234216  2008-04-0717:42 inkscape
    -rwxr-xr-x 1 root   root   8222692  2008-04-07 17:42 inkview
    ...
    

    sort 程序的许多用法都涉及到处理表格数据,例如上面 ls 命令的输出结果。如果我们 把数据库这个术语应用到上面的表格中,我们会说每行是一条记录,并且每条记录由多个字段组成, 例如文件属性,链接数,文件名,文件大小等等。sort 程序能够处理独立的字段。在数据库术语中, 我们能够指定一个或者多个关键字段,来作为排序的关键值。在上面的例子中,我们指定 n 和 r 选项来执行相反的数值排序,并且指定 -k 5,让 sort 程序使用第五字段作为排序的关键值。

    这个 k 选项非常有趣,而且还有很多特点,但是首先我们需要讲讲 sort 程序怎样来定义字段。 让我们考虑一个非常简单的文本文件,只有一行包含作者名字的文本。

    William      Shotts
    

    默认情况下,sort 程序把此行看作有两个字段。第一个字段包含字符:

    和第二个字段包含字符:

    意味着空白字符(空格和制表符)被当作是字段间的界定符,当执行排序时,界定符会被 包含在字段当中。再看一下 ls 命令的输出,我们看到每行包含八个字段,并且第五个字段是文件大小:

    -rwxr-xr-x 1 root root 8234216 2008-04-07 17:42 inkscape
    

    让我们考虑用下面的文件,其包含从 2006 年到 2008 年三款流行的 Linux 发行版的发行历史,来做一系列实验。 文件中的每一行都有三个字段:发行版的名称,版本号,和 MM/DD/YYYY 格式的发行日期:

    SUSE        10.2   12/07/2006
    Fedora          10     11/25/2008
    SUSE            11.04  06/19/2008
    Ubuntu          8.04   04/24/2008
    Fedora          8      11/08/2007
    SUSE            10.3   10/04/2007
    ...
    

    使用一个文本编辑器(可能是 vim),我们将输入这些数据,并把产生的文件命名为 distros.txt。

    下一步,我们将试着对这个文件进行排序,并观察输出结果:

    [me@linuxbox ~]$ sort distros.txt
    Fedora          10     11/25/2008
    Fedora          5     03/20/2006
    Fedora          6     10/24/2006
    Fedora          7     05/31/2007
    Fedora          8     11/08/2007
    ...
    

    恩,大部分正确。问题出现在 Fedora 的版本号上。因为在字符集中 “1” 出现在 “5” 之前,版本号 “10” 在 最顶端,然而版本号 “9” 却掉到底端。

    为了解决这个问题,我们必须依赖多个键值来排序。我们想要对第一个字段执行字母排序,然后对 第三个字段执行数值排序。sort 程序允许多个 -k 选项的实例,所以可以指定多个排序关键值。事实上, 一个关键值可能包括一个字段区域。如果没有指定区域(如同之前的例子),sort 程序会使用一个键值, 其始于指定的字段,一直扩展到行尾。下面是多键值排序的语法:

    [me@linuxbox ~]$ sort --key=1,1 --key=2n distros.txt
    Fedora         5     03/20/2006
    Fedora         6     10/24/2006
    Fedora         7     05/31/2007
    ...
    

    虽然为了清晰,我们使用了选项的长格式,但是 -k 1,1 -k 2n 格式是等价的。在第一个 key 选项的实例中, 我们指定了一个字段区域。因为我们只想对第一个字段排序,我们指定了 1,1, 意味着“始于并且结束于第一个字段。”在第二个实例中,我们指定了 2n,意味着第二个字段是排序的键值, 并且按照数值排序。一个选项字母可能被包含在一个键值说明符的末尾,其用来指定排序的种类。这些 选项字母和 sort 程序的全局选项一样:b(忽略开头的空格),n(数值排序),r(逆向排序),等等。

    我们列表中第三个字段包含的日期格式不利于排序。在计算机中,日期通常设置为 YYYY-MM-DD 格式, 这样使按时间顺序排序变得容易,但是我们的日期为美国格式 MM/DD/YYYY。那么我们怎样能按照 时间顺序来排列这个列表呢?

    幸运地是,sort 程序提供了一种方式。这个 key 选项允许在字段中指定偏移量,所以我们能在字段中 定义键值。

    [me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt
    Fedora         10    11/25/2008
    Ubuntu         8.10  10/30/2008
    SUSE           11.0  06/19/2008
    ...
    

    通过指定 -k 3.7,我们指示 sort 程序使用一个排序键值,其始于第三个字段中的第七个字符,对应于 年的开头。同样地,我们指定 -k 3.1和 -k 3.4来分离日期中的月和日。 我们也添加了 n 和 r 选项来实现一个逆向的数值排序。这个 b 选项用来删除日期字段中开头的空格( 行与行之间的空格数迥异,因此会影响 sort 程序的输出结果)。

    一些文件不会使用 tabs 和空格做为字段界定符;例如,这个 /etc/passwd 文件:

    [me@linuxbox ~]$ head /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    daemon:x:1:1:daemon:/usr/sbin:/bin/sh
    bin:x:2:2:bin:/bin:/bin/sh
    sys:x:3:3:sys:/dev:/bin/sh
    sync:x:4:65534:sync:/bin:/bin/sync
    games:x:5:60:games:/usr/games:/bin/sh
    man:x:6:12:man:/var/cache/man:/bin/sh
    lp:x:7:7:lp:/var/spool/lpd:/bin/sh
    mail:x:8:8:mail:/var/mail:/bin/sh
    news:x:9:9:news:/var/spool/news:/bin/sh
    

    这个文件的字段之间通过冒号分隔开,所以我们怎样使用一个 key 字段来排序这个文件?sort 程序提供 了一个 -t 选项来定义分隔符。按照第七个字段(帐户的默认 shell)来排序此 passwd 文件,我们可以这样做:

    [me@linuxbox ~]$ sort -t ':' -k 7 /etc/passwd | head
    me:x:1001:1001:Myself,,,:/home/me:/bin/bash
    root:x:0:0:root:/root:/bin/bash
    dhcp:x:101:102::/nonexistent:/bin/false
    gdm:x:106:114:Gnome Display Manager:/var/lib/gdm:/bin/false
    hplip:x:104:7:HPLIP system user,,,:/var/run/hplip:/bin/false
    klog:x:103:104::/home/klog:/bin/false
    messagebus:x:108:119::/var/run/dbus:/bin/false
    polkituser:x:110:122:PolicyKit,,,:/var/run/PolicyKit:/bin/false
    pulse:x:107:116:PulseAudio daemon,,,:/var/run/pulse:/bin/false
    

    通过指定冒号字符做为字段分隔符,我们能按照第七个字段来排序。

    uniq

    与 sort 程序相比,这个 uniq 程序是个轻量级程序。uniq 执行一个看似琐碎的认为。当给定一个 排好序的文件(包括标准输出),uniq 会删除任意重复行,并且把结果发送到标准输出。 它常常和 sort 程序一块使用,来清理重复的输出。


    uniq 程序是一个传统的 Unix 工具,经常与 sort 程序一块使用,但是这个 GNU 版本的 sort 程序支持一个 -u 选项,其可以从排好序的输出结果中删除重复行。


    让我们创建一个文本文件,来实验一下:

    [me@linuxbox ~]$ cat > foo.txt
    a
    b
    c
    a
    b
    c
    

    记住输入 Ctrl-d 来终止标准输入。现在,如果我们对文本文件执行 uniq 命令:

    [me@linuxbox ~]$ uniq foo.txt
    a
    b
    c
    a
    b
    c
    

    输出结果与原始文件没有差异;重复行没有被删除。实际上,uniq 程序能完成任务,其输入必须是排好序的数据,

    [me@linuxbox ~]$ sort foo.txt | uniq
    a
    b
    c
    

    这是因为 uniq 只会删除相邻的重复行。uniq 程序有几个选项。这里是一些常用选项:

    表21-2: 常用的 uniq 选项

    选项说明
    -c输出所有的重复行,并且每行开头显示重复的次数。
    -d只输出重复行,而不是特有的文本行。
    -f n忽略每行开头的 n 个字段,字段之间由空格分隔,正如 sort 程序中的空格分隔符;然而, 不同于 sort 程序,uniq 没有选项来设置备用的字段分隔符。
    -i在比较文本行的时候忽略大小写。
    -s n跳过(忽略)每行开头的 n 个字符。
    -u只是输出独有的文本行。这是默认的。

    这里我们看到 uniq 被用来报告文本文件中重复行的次数,使用这个-c 选项:

    [me@linuxbox ~]$ sort foo.txt | uniq -c
            2 a
            2 b
            2 c
    

    切片和切块

    下面我们将要讨论的三个程序用来从文件中获得文本列,并且以有用的方式重组它们。

    cut

    这个 cut 程序被用来从文本行中抽取文本,并把其输出到标准输出。它能够接受多个文件参数或者 标准输入。

    从文本行中指定要抽取的文本有些麻烦,使用以下选项:

    表21-3: cut 程序选择项

    选项说明
    -c char_list从文本行中抽取由 char_list 定义的文本。这个列表可能由一个或多个逗号 分隔开的数值区间组成。
    -f field_list从文本行中抽取一个或多个由 field_list 定义的字段。这个列表可能 包括一个或多个字段,或由逗号分隔开的字段区间。
    -d delim_char当指定-f 选项之后,使用 delim_char 做为字段分隔符。默认情况下, 字段之间必须由单个 tab 字符分隔开。
    --complement抽取整个文本行,除了那些由-c 和/或-f 选项指定的文本。

    正如我们所看到的,cut 程序抽取文本的方式相当不灵活。cut 命令最好用来从其它程序产生的文件中 抽取文本,而不是从人们直接输入的文本中抽取。我们将会看一下我们的 distros.txt 文件,看看 是否它足够 “整齐” 成为 cut 实例的一个好样本。如果我们使用带有 -A 选项的 cat 命令,我们能查看是否这个 文件符号由 tab 字符分离字段的要求。

    [me@linuxbox ~]$ cat -A distros.txt
    SUSE^I10.2^I12/07/2006$
    Fedora^I10^I11/25/2008$
    SUSE^I11.0^I06/19/2008$
    Ubuntu^I8.04^I04/24/2008$
    Fedora^I8^I11/08/2007$
    SUSE^I10.3^I10/04/2007$
    Ubuntu^I6.10^I10/26/2006$
    Fedora^I7^I05/31/2007$
    Ubuntu^I7.10^I10/18/2007$
    Ubuntu^I7.04^I04/19/2007$
    SUSE^I10.1^I05/11/2006$
    Fedora^I6^I10/24/2006$
    Fedora^I9^I05/13/2008$
    Ubuntu^I6.06^I06/01/2006$
    Ubuntu^I8.10^I10/30/2008$
    Fedora^I5^I03/20/2006$
    

    看起来不错。字段之间仅仅是单个 tab 字符,没有嵌入空格。因为这个文件使用了 tab 而不是空格, 我们将使用 -f 选项来抽取一个字段:

    [me@linuxbox ~]$ cut -f 3 distros.txt
    12/07/2006
    11/25/2008
    06/19/2008
    04/24/2008
    11/08/2007
    10/04/2007
    10/26/2006
    05/31/2007
    10/18/2007
    04/19/2007
    05/11/2006
    10/24/2006
    05/13/2008
    06/01/2006
    10/30/2008
    03/20/2006
    

    因为我们的 distros 文件是由 tab 分隔开的,最好用 cut 来抽取字段而不是字符。这是因为一个由 tab 分离的文件, 每行不太可能包含相同的字符数,这就使计算每行中字符的位置变得困难或者是不可能。在以上事例中,然而, 我们已经抽取了一个字段,幸运地是其包含地日期长度相同,所以通过从每行中抽取年份,我们能展示怎样 来抽取字符:

    [me@linuxbox ~]$ cut -f 3 distros.txt | cut -c 7-10
    2006
    2008
    2007
    2006
    2007
    2006
    2008
    2006
    2008
    2006
    

    通过对我们的列表再次运行 cut 命令,我们能够抽取从位置7到10的字符,其对应于日期字段的年份。 这个 7-10 表示法是一个区间的例子。cut 命令手册包含了一个如何指定区间的完整描述。

    展开 Tabs

    distros.txt 的文件格式很适合使用 cut 程序来抽取字段。但是如果我们想要 cut 程序 按照字符,而不是字段来操作一个文件,那又怎样呢?这要求我们用相应数目的空格来 代替 tab 字符。幸运地是,GNU 的 Coreutils 软件包有一个工具来解决这个问题。这个 程序名为 expand,它既可以接受一个或多个文件参数,也可以接受标准输入,并且把 修改过的文本送到标准输出。

    如果我们通过 expand 来处理 distros.txt 文件,我们能够使用 cut -c 命令来从文件中抽取 任意区间内的字符。例如,我们能够使用以下命令来从列表中抽取发行年份,通过展开 此文件,再使用 cut 命令,来抽取从位置 23 开始到行尾的每一个字符:

    [me@linuxbox ~]$ expand distros.txt | cut -c 23-

    Coreutils 软件包也提供了 unexpand 程序,用 tab 来代替空格。

    当操作字段的时候,有可能指定不同的字段分隔符,而不是 tab 字符。这里我们将会从/etc/passwd 文件中 抽取第一个字段:

    [me@linuxbox ~]$ cut -d ':' -f 1 /etc/passwd | head
    root
    daemon
    bin
    sys
    sync
    games
    man
    lp
    mail
    news
    

    使用-d 选项,我们能够指定冒号做为字段分隔符。

    paste

    这个 paste 命令的功能正好与 cut 相反。它会添加一个或多个文本列到文件中,而不是从文件中抽取文本列。 它通过读取多个文件,然后把每个文件中的字段整合成单个文本流,输入到标准输出。类似于 cut 命令, paste 接受多个文件参数和 / 或标准输入。为了说明 paste 是怎样工作的,我们将会对 distros.txt 文件 动手术,来产生发行版的年代表。

    从我们之前使用 sort 的工作中,首先我们将产生一个按照日期排序的发行版列表,并把结果 存储在一个叫做 distros-by-date.txt 的文件中:

    [me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt > distros-by-date.txt
    

    下一步,我们将会使用 cut 命令从文件中抽取前两个字段(发行版名字和版本号),并把结果存储到 一个名为 distro-versions.txt 的文件中:

    [me@linuxbox ~]$ cut -f 1,2 distros-by-date.txt > distros-versions.txt
    [me@linuxbox ~]$ head distros-versions.txt
    Fedora     10
    Ubuntu     8.10
    SUSE       11.0
    Fedora     9
    Ubuntu     8.04
    Fedora     8
    Ubuntu     7.10
    SUSE       10.3
    Fedora     7
    Ubuntu     7.04
    

    最后的准备步骤是抽取发行日期,并把它们存储到一个名为 distro-dates.txt 文件中:

    [me@linuxbox ~]$ cut -f 3 distros-by-date.txt > distros-dates.txt
    [me@linuxbox ~]$ head distros-dates.txt
    11/25/2008
    10/30/2008
    06/19/2008
    05/13/2008
    04/24/2008
    11/08/2007
    10/18/2007
    10/04/2007
    05/31/2007
    04/19/2007
    

    现在我们拥有了我们所需要的文本了。为了完成这个过程,使用 paste 命令来把日期列放到发行版名字 和版本号的前面,这样就创建了一个年代列表。通过使用 paste 命令,然后按照期望的顺序来安排它的 参数,就能很容易完成这个任务。

    [me@linuxbox ~]$ paste distros-dates.txt distros-versions.txt
    11/25/2008	Fedora     10
    10/30/2008	Ubuntu     8.10
    06/19/2008	SUSE       11.0
    05/13/2008	Fedora     9
    04/24/2008	Ubuntu     8.04
    11/08/2007	Fedora     8
    10/18/2007	Ubuntu     7.10
    10/04/2007	SUSE       10.3
    05/31/2007	Fedora     7
    04/19/2007	Ubuntu     7.04
    

    join

    在某些方面,join 命令类似于 paste,它会往文件中添加列,但是它使用了独特的方法来完成。 一个 join 操作通常与关系型数据库有关联,在关系型数据库中来自多个享有共同关键域的表格的 数据结合起来,得到一个期望的结果。这个 join 程序执行相同的操作。它把来自于多个基于共享 关键域的文件的数据结合起来。

    为了知道在关系数据库中是怎样使用 join 操作的,让我们想象一个很小的数据库,这个数据库由两个 表格组成,每个表格包含一条记录。第一个表格,叫做 CUSTOMERS,有三个数据域:一个客户号(CUSTNUM), 客户的名字(FNAME)和客户的姓(LNAME):

    CUSTNUM	    FNAME       ME
    ========    =====       ======
    4681934	    John        Smith
    

    第二个表格叫做 ORDERS,其包含四个数据域:订单号(ORDERNUM),客户号(CUSTNUM),数量(QUAN), 和订购的货品(ITEM)。

    ORDERNUM        CUSTNUM     QUAN ITEM
    ========        =======     ==== ====
    3014953305      4681934     1    Blue Widget
    

    注意两个表格共享数据域 CUSTNUM。这很重要,因为它使表格之间建立了联系。

    执行一个 join 操作将允许我们把两个表格中的数据域结合起来,得到一个有用的结果,例如准备 一张发货单。通过使用两个表格 CUSTNUM 数字域中匹配的数值,一个 join 操作会产生以下结果:

    FNAME       LNAME       QUAN ITEM
    =====       =====       ==== ====
    John        Smith       1    Blue Widget
    

    为了说明 join 程序,我们需要创建一对包含共享键值的文件。为此,我们将使用我们的 distros.txt 文件。 从这个文件中,我们将构建额外两个文件,一个包含发行日期(其会成为共享键值)和发行版名称:

    [me@linuxbox ~]$ cut -f 1,1 distros-by-date.txt > distros-names.txt
    [me@linuxbox ~]$ paste distros-dates.txt distros-names.txt > distros-key-names.txt
    [me@linuxbox ~]$ head distros-key-names.txt
    11/25/2008 Fedora
    10/30/2008 Ubuntu
    06/19/2008 SUSE
    05/13/2008 Fedora
    04/24/2008 Ubuntu
    11/08/2007 Fedora
    10/18/2007 Ubuntu
    10/04/2007 SUSE
    05/31/2007 Fedora
    04/19/2007 Ubuntu
    

    第二个文件包含发行日期和版本号:

    [me@linuxbox ~]$ cut -f 2,2 distros-by-date.txt > distros-vernums.txt
    [me@linuxbox ~]$ paste distros-dates.txt distros-vernums.txt > distros-key-vernums.txt
    [me@linuxbox ~]$ head distros-key-vernums.txt
    11/25/2008 10
    10/30/2008 8.10
    06/19/2008 11.0
    05/13/2008 9
    04/24/2008 8.04
    11/08/2007 8
    10/18/2007 7.10
    10/04/2007 10.3
    05/31/2007 7
    04/19/2007 7.04
    

    现在我们有两个具有共享键值( “发行日期” 数据域 )的文件。有必要指出,为了使 join 命令 能正常工作,所有文件必须按照关键数据域排序。

    [me@linuxbox ~]$ join distros-key-names.txt distros-key-vernums.txt | head
    11/25/2008 Fedora 10
    10/30/2008 Ubuntu 8.10
    06/19/2008 SUSE 11.0
    05/13/2008 Fedora 9
    04/24/2008 Ubuntu 8.04
    11/08/2007 Fedora 8
    10/18/2007 Ubuntu 7.10
    10/04/2007 SUSE 10.3
    05/31/2007 Fedora 7
    04/19/2007 Ubuntu 7.04
    

    也要注意,默认情况下,join 命令使用空白字符做为输入字段的界定符,一个空格作为输出字段 的界定符。这种行为可以通过指定的选项来修改。详细信息,参考 join 命令手册。

    比较文本

    通常比较文本文件的版本很有帮助。对于系统管理员和软件开发者来说,这个尤为重要。 一名系统管理员可能,例如,需要拿现有的配置文件与先前的版本做比较,来诊断一个系统错误。 同样的,一名程序员经常需要查看程序的修改。

    comm

    这个 comm 程序会比较两个文本文件,并且会显示每个文件特有的文本行和共有的文把行。 为了说明问题,通过使用 cat 命令,我们将会创建两个内容几乎相同的文本文件:

    [me@linuxbox ~]$ cat > file1.txt
    a
    b
    c
    d
    [me@linuxbox ~]$ cat > file2.txt
    b
    c
    d
    e
    

    下一步,我们将使用 comm 命令来比较这两个文件:

    [me@linuxbox ~]$ comm file1.txt file2.txt
    a
            b
            c
            d
        e
    

    正如我们所见到的,comm 命令产生了三列输出。第一列包含第一个文件独有的文本行;第二列, 文本行是第二列独有的;第三列包含两个文件共有的文本行。comm 支持 -n 形式的选项,这里 n 代表 1,2 或 3。这些选项使用的时候,指定了要隐藏的列。例如,如果我们只想输出两个文件共享的文本行, 我们将隐藏第一列和第二列的输出结果:

    [me@linuxbox ~]$ comm -12 file1.txt file2.txt
    b
    c
    d
    

    diff

    类似于 comm 程序,diff 程序被用来监测文件之间的差异。然而,diff 是一款更加复杂的工具,它支持 许多输出格式,并且一次能处理许多文本文件。软件开发员经常使用 diff 程序来检查不同程序源码 版本之间的更改,diff 能够递归地检查源码目录,经常称之为源码树。diff 程序的一个常见用例是 创建 diff 文件或者补丁,它会被其它程序使用,例如 patch 程序(我们一会儿讨论),来把文件 从一个版本转换为另一个版本。

    如果我们使用 diff 程序,来查看我们之前的文件实例:

    [me@linuxbox ~]$ diff file1.txt file2.txt
    1d0
    < a
    4a4
    > e
    

    我们看到 diff 程序的默认输出风格:对两个文件之间差异的简短描述。在默认格式中, 每组的更改之前都是一个更改命令,其形式为 range operation range , 用来描述要求更改的位置和类型,从而把第一个文件转变为第二个文件:

    表21-4: diff 更改命令

    改变说明
    r1ar2把第二个文件中位置 r2 处的文件行添加到第一个文件中的 r1 处。
    r1cr2用第二个文件中位置 r2 处的文本行更改(替代)位置 r1 处的文本行。
    r1dr2删除第一个文件中位置 r1 处的文本行,这些文本行将会出现在第二个文件中位置 r2 处。

    在这种格式中,一个范围就是由逗号分隔开的开头行和结束行的列表。虽然这种格式是默认情况(主要是 为了服从 POSIX 标准且向后与传统的 Unix diff 命令兼容), 但是它并不像其它可选格式一样被广泛地使用。最流行的两种格式是上下文模式和统一模式。

    当使用上下文模式(带上 -c 选项),我们将看到这些:

    [me@linuxbox ~]$ diff -c file1.txt file2.txt
    *** file1.txt    2008-12-23 06:40:13.000000000 -0500
    --- file2.txt   2008-12-23 06:40:34.000000000 -0500
    ***************
    *** 1,4 ****
    - a
      b
      c
      d
    --- 1,4 ----
      b
      c
      d
      + e
    

    这个输出结果以两个文件名和它们的时间戳开头。第一个文件用星号做标记,第二个文件用短横线做标记。 纵观列表的其它部分,这些标记将象征它们各自代表的文件。下一步,我们看到几组修改, 包括默认的周围上下文行数。在第一组中,我们看到:

    *** 1,4 ***
    

    其表示第一个文件中从第一行到第四行的文本行。随后我们看到:

    --- 1,4 ---
    

    这表示第二个文件中从第一行到第四行的文本行。在更改组内,文本行以四个指示符之一开头:

    表21-5: diff 上下文模式更改指示符

    指示符意思
    blank上下文显示行。它并不表示两个文件之间的差异。
    -删除行。这一行将会出现在第一个文件中,而不是第二个文件内。
    +添加行。这一行将会出现在第二个文件内,而不是第一个文件中。
    !更改行。将会显示某个文本行的两个版本,每个版本会出现在更改组的各自部分。

    这个统一模式相似于上下文模式,但是更加简洁。通过 -u 选项来指定它:

    [me@linuxbox ~]$ diff -u file1.txt file2.txt
    --- file1.txt 2008-12-23 06:40:13.000000000 -0500
    +++ file2.txt 2008-12-23 06:40:34.000000000 -0500
    @@ -1,4 +1,4 @@
    -a
     b
     c
     d
    +e
    

    上下文模式和统一模式之间最显著的差异就是重复上下文的消除,这就使得统一模式的输出结果要比上下文 模式的输出结果简短。在我们上述实例中,我们看到类似于上下文模式中的文件时间戳,其紧紧跟随字符串 @@ -1,4 +1,4 @@。这行字符串表示了在更改组中描述的第一个文件中的文本行和第二个文件中的文本行。 这行字符串之后就是文本行本身,与三行默认的上下文。每行以可能的三个字符中的一个开头:

    表21-6: diff 统一模式更改指示符

    字符意思
    空格两个文件都包含这一行。
    -在第一个文件中删除这一行。
    +添加这一行到第一个文件中。

    patch

    这个 patch 程序被用来把更改应用到文本文件中。它接受从 diff 程序的输出,并且通常被用来 把较老的文件版本转变为较新的文件版本。让我们考虑一个著名的例子。Linux 内核是由一个 大型的,组织松散的贡献者团队开发而成,这些贡献者会提交固定的少量更改到源码包中。 这个 Linux 内核由几百万行代码组成,虽然每个贡献者每次所做的修改相当少。对于一个贡献者 来说,每做一个修改就给每个开发者发送整个的内核源码树,这是没有任何意义的。相反, 提交一个 diff 文件。一个 diff 文件包含先前的内核版本与带有贡献者修改的新版本之间的差异。 然后一个接受者使用 patch 程序,把这些更改应用到他自己的源码树中。使用 diff/patch 组合提供了 两个重大优点:

    1. 一个 diff 文件非常小,与整个源码树的大小相比较而言。

    2. 一个 diff 文件简洁地显示了所做的修改,从而允许程序补丁的审阅者能快速地评估它。

    当然,diff/patch 能工作于任何文本文件,不仅仅是源码文件。它同样适用于配置文件或任意其它文本。

    准备一个 diff 文件供 patch 程序使用,GNU 文档(查看下面的拓展阅读部分)建议这样使用 diff 命令:

    diff -Naur old_file new_file > diff_file
    

    old_file 和 new_file 部分不是单个文件就是包含文件的目录。这个 r 选项支持递归目录树。

    一旦创建了 diff 文件,我们就能应用它,把旧文件修补成新文件。

    patch < diff_file
    

    我们将使用测试文件来说明:

    [me@linuxbox ~]$ diff -Naur file1.txt file2.txt &gt; patchfile.txt
    [me@linuxbox ~]$ patch &lt; patchfile.txt
    patching file file1.txt
    [me@linuxbox ~]$ cat file1.txt
    b
    c
    d
    e
    

    在这个例子中,我们创建了一个名为 patchfile.txt 的 diff 文件,然后使用 patch 程序, 来应用这个补丁。注意我们没有必要指定一个要修补的目标文件,因为 diff 文件(在统一模式中)已经 在标题行中包含了文件名。一旦应用了补丁,我们能看到,现在 file1.txt 与 file2.txt 文件相匹配了。

    patch 程序有大量的选项,而且还有额外的实用程序可以被用来分析和编辑补丁。

    运行时编辑

    我们对于文本编辑器的经验是它们主要是交互式的,意思是我们手动移动光标,然后输入我们的修改。 然而,也有非交互式的方法来编辑文本。有可能,例如,通过单个命令把一系列修改应用到多个文件中。

    tr

    这个 tr 程序被用来更改字符。我们可以把它看作是一种基于字符的查找和替换操作。 换字是一种把字符从一个字母转换为另一个字母的过程。例如,把小写字母转换成大写字母就是 换字。我们可以通过 tr 命令来执行这样的转换,如下所示:

    [me@linuxbox ~]$ echo "lowercase letters" | tr a-z A-Z
    LOWERCASE LETTERS
    

    正如我们所见,tr 命令操作标准输入,并把结果输出到标准输出。tr 命令接受两个参数:要被转换的字符集以及 相对应的转换后的字符集。字符集可以用三种方式来表示:

    1. 一个枚举列表。例如, ABCDEFGHIJKLMNOPQRSTUVWXYZ

    2. 一个字符域。例如,A-Z 。注意这种方法有时候面临与其它命令相同的问题,归因于 语系的排序规则,因此应该谨慎使用。

    3. POSIX 字符类。例如,[:upper:]

    大多数情况下,两个字符集应该长度相同;然而,有可能第一个集合大于第二个,尤其如果我们 想要把多个字符转换为单个字符:

    [me@linuxbox ~]$ echo "lowercase letters" | tr [:lower:] A
    AAAAAAAAA AAAAAAA
    

    除了换字之外,tr 命令能允许字符从输入流中简单地被删除。在之前的章节中,我们讨论了转换 MS-DOS 文本文件为 Unix 风格文本的问题。为了执行这个转换,每行末尾的回车符需要被删除。 这个可以通过 tr 命令来执行,如下所示:

    tr -d '\r' < dos_file > unix_file
    

    这里的 dos_file 是需要被转换的文件,unix_file 是转换后的结果。这种形式的命令使用转义序列 \r 来代表回车符。查看 tr 命令所支持地完整的转义序列和字符类别列表,试试下面的命令:

    [me@linuxbox ~]$ tr --help
    

    ROT13: 不那么秘密的编码环

    tr 命令的一个有趣的用法是执行 ROT13文本编码。ROT13是一款微不足道的基于一种简易的替换暗码的 加密类型。把 ROT13称为“加密”是大方的;“文本模糊处理”更准确些。有时候它被用来隐藏文本中潜在的攻击内容。 这个方法就是简单地把每个字符在字母表中向前移动13位。因为移动的位数是可能的26个字符的一半, 所以对文本再次执行这个算法,就恢复到了它最初的形式。通过 tr 命令来执行这种编码:

    echo “secret text” | tr a-zA-Z n-za-mN-ZA-M |

    frperg grkg

    再次执行相同的过程,得到翻译结果:

    | _echo “frperg grkg” | tr a-zA-Z n-za-mN-ZA-M+ |

    secret text

    大量的 email 程序和 USENET 新闻读者都支持 ROT13 编码。Wikipedia 上面有一篇关于这个主题的好文章:

    http://en.wikipedia.org/wiki/ROT13

    tr 也可以完成另一个技巧。使用-s 选项,tr 命令能“挤压”(删除)重复的字符实例:

    [me@linuxbox ~]$ echo "aaabbbccc" | tr -s ab
    abccc
    

    这里我们有一个包含重复字符的字符串。通过给 tr 命令指定字符集“ab”,我们能够消除字符集中 字母的重复实例,然而会留下不属于字符集的字符(“c”)无更改。注意重复的字符必须是相邻的。 如果它们不相邻:

    [me@linuxbox ~]$ echo "abcabcabc" | tr -s ab
    abcabcabc
    

    那么挤压会没有效果。

    sed

    名字 sed 是 stream editor(流编辑器)的简称。它对文本流进行编辑,要不是一系列指定的文件, 要不就是标准输入。sed 是一款强大的,并且有些复杂的程序(有整本内容都是关于 sed 程序的书籍), 所以在这里我们不会详尽的讨论它。

    总之,sed 的工作方式是要不给出单个编辑命令(在命令行中)要不就是包含多个命令的脚本文件名, 然后它就按行来执行这些命令。这里有一个非常简单的 sed 实例:

    [me@linuxbox ~]$ echo "front" | sed 's/front/back/'
    back
    

    在这个例子中,我们使用 echo 命令产生了一个单词的文本流,然后把它管道给 sed 命令。sed,依次, 对流文本执行指令 s/front/back/,随后输出“back”。我们也能够把这个命令认为是相似于 vi 中的“替换” (查找和替代)命令。

    sed 中的命令开始于单个字符。在上面的例子中,这个替换命令由字母 s 来代表,其后跟着查找 和替代字符串,斜杠字符做为分隔符。分隔符的选择是随意的。按照惯例,经常使用斜杠字符, 但是 sed 将会接受紧随命令之后的任意字符做为分隔符。我们可以按照这种方式来执行相同的命令:

    [me@linuxbox ~]$ echo "front" | sed 's\_front\_back\_'
    back
    

    通过紧跟命令之后使用下划线字符,则它变成界定符。sed 可以设置界定符的能力,使命令的可读性更强, 正如我们将看到的.

    sed 中的大多数命令之前都会带有一个地址,其指定了输入流中要被编辑的文本行。如果省略了地址, 然后会对输入流的每一行执行编辑命令。最简单的地址形式是一个行号。我们能够添加一个地址 到我们例子中:

    [me@linuxbox ~]$ echo "front" | sed '1s/front/back/'
    back
    

    给我们的命令添加地址 1,就导致只对仅有一行文本的输入流的第一行执行替换操作。如果我们指定另一 个数字:

    [me@linuxbox ~]$ echo "front" | sed '2s/front/back/'
    front
    

    我们看到没有执行这个编辑命令,因为我们的输入流没有第二行。地址可以用许多方式来表达。这里是 最常用的:

    表21-7: sed 地址表示法

    地址说明
    n行号,n 是一个正整数。
    $最后一行。
    /regexp/所有匹配一个 POSIX 基本正则表达式的文本行。注意正则表达式通过 斜杠字符界定。选择性地,这个正则表达式可能由一个备用字符界定,通过\cregexpc 来 指定表达式,这里 c 就是一个备用的字符。
    addr1,addr2从 addr1 到 addr2 范围内的文本行,包含地址 addr2 在内。地址可能是上述任意 单独的地址形式。
    first~step匹配由数字 first 代表的文本行,然后随后的每个在 step 间隔处的文本行。例如 1~2 是指每个位于偶数行号的文本行,5~5 则指第五行和之后每五行位置的文本行。
    addr1,+n匹配地址 addr1 和随后的 n 个文本行。
    addr!匹配所有的文本行,除了 addr 之外,addr 可能是上述任意的地址形式。

    通过使用这一章中早前的 distros.txt 文件,我们将演示不同种类的地址表示法。首先,一系列行号:

    [me@linuxbox ~]$ sed -n '1,5p' distros.txt
    SUSE           10.2     12/07/2006
    Fedora         10       11/25/2008
    SUSE           11.0     06/19/2008
    Ubuntu         8.04     04/24/2008
    Fedora         8        11/08/2007
    

    在这个例子中,我们打印出一系列的文本行,开始于第一行,直到第五行。为此,我们使用 p 命令, 其就是简单地把匹配的文本行打印出来。然而为了高效,我们必须包含选项 -n(不自动打印选项), 让 sed 不要默认地打印每一行。

    下一步,我们将试用一下正则表达式:

    [me@linuxbox ~]$ sed -n '/SUSE/p' distros.txt
    SUSE         10.2     12/07/2006
    SUSE         11.0     06/19/2008
    SUSE         10.3     10/04/2007
    SUSE         10.1     05/11/2006
    

    通过包含由斜杠界定的正则表达式 /SUSE/,我们能够孤立出包含它的文本行,和 grep 程序的功能 是相同的。

    最后,我们将试着否定上面的操作,通过给这个地址添加一个感叹号:

    [me@linuxbox ~]$ sed -n '/SUSE/!p' distros.txt
    Fedora         10       11/25/2008
    Ubuntu         8.04     04/24/2008
    Fedora         8        11/08/2007
    Ubuntu         6.10     10/26/2006
    Fedora         7        05/31/2007
    Ubuntu         7.10     10/18/2007
    Ubuntu         7.04     04/19/2007
    Fedora         6        10/24/2006
    Fedora         9        05/13/2008
    Ubuntu         6.06     06/01/2006
    Ubuntu         8.10     10/30/2008
    Fedora         5        03/20/2006
    

    这里我们看到期望的结果:输出了文件中所有的文本行,除了那些匹配这个正则表达式的文本行。

    目前为止,我们已经知道了两个 sed 的编辑命令,s 和 p。这里是一个更加全面的基本编辑命令列表:

    表21-8: sed 基本编辑命令

    命令说明
    =输出当前的行号。
    a在当前行之后追加文本。
    d删除当前行。
    i在当前行之前插入文本。
    p打印当前行。默认情况下,sed 程序打印每一行,并且只是编辑文件中匹配 指定地址的文本行。通过指定-n 选项,这个默认的行为能够被忽略。
    q退出 sed,不再处理更多的文本行。如果不指定-n 选项,输出当前行。
    Q退出 sed,不再处理更多的文本行。
    s/regexp/replacement/只要找到一个 regexp 匹配项,就替换为 replacement 的内容。 replacement 可能包括特殊字符 &,其等价于由 regexp 匹配的文本。另外, replacement 可能包含序列 \1到 \9,其是 regexp 中相对应的子表达式的内容。更多信息,查看 下面 back references 部分的讨论。在 replacement 末尾的斜杠之后,可以指定一个 可选的标志,来修改 s 命令的行为。
    y/set1/set2执行字符转写操作,通过把 set1 中的字符转变为相对应的 set2 中的字符。 注意不同于 tr 程序,sed 要求两个字符集合具有相同的长度。

    到目前为止,这个 s 命令是最常使用的编辑命令。我们将仅仅演示一些它的功能,通过编辑我们的 distros.txt 文件。我们以前讨论过 distros.txt 文件中的日期字段不是“友好地计算机”模式。 文件中的日期格式是 MM/DD/YYYY,但如果格式是 YYYY-MM-DD 会更好一些(利于排序)。手动修改 日期格式不仅浪费时间而且易出错,但是有了 sed,只需一步就能完成修改:

    [me@linuxbox ~]$ sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt
    SUSE           10.2     2006-12-07
    Fedora         10       2008-11-25
    SUSE           11.0     2008-06-19
    Ubuntu         8.04     2008-04-24
    Fedora         8        2007-11-08
    SUSE           10.3     2007-10-04
    Ubuntu         6.10     2006-10-26
    Fedora         7        2007-05-31
    Ubuntu         7.10     2007-10-18
    Ubuntu         7.04     2007-04-19
    SUSE           10.1     2006-05-11
    Fedora         6        2006-10-24
    Fedora         9        2008-05-13
    Ubuntu         6.06     2006-06-01
    Ubuntu         8.10     2008-10-30
    Fedora         5        2006-03-20
    

    哇!这个命令看起来很丑陋。但是它起作用了。仅用一步,我们就更改了文件中的日期格式。 它也是一个关于为什么有时候会开玩笑地把正则表达式称为是“只写”媒介的完美的例子。我们 能写正则表达式,但是有时候我们不能读它们。在我们恐惧地忍不住要逃离此命令之前,让我们看一下 怎样来构建它。首先,我们知道此命令有这样一个基本的结构:

    sed 's/regexp/replacement/' distros.txt
    

    我们下一步是要弄明白一个正则表达式将要孤立出日期。因为日期是 MM/DD/YYYY 格式,并且 出现在文本行的末尾,我们可以使用这样的表达式:

    [0-9]{2}/[0-9]{2}/[0-9]{4}$
    

    此表达式匹配两位数字,一个斜杠,两位数字,一个斜杠,四位数字,以及行尾。如此关心_regexp, 那么_replacement_又怎样呢?为了解决此问题,我们必须介绍一个正则表达式的新功能,它出现 在一些使用 BRE 的应用程序中。这个功能叫做_逆参照,像这样工作:如果序列\n 出现在_replacement_中 ,这里 n 是指从 1 到 9 的数字,则这个序列指的是在前面正则表达式中相对应的子表达式。为了 创建这个子表达式,我们简单地把它们用圆括号括起来,像这样:

    ([0-9]{2})/([0-9]{2})/([0-9]{4})$
    

    现在我们有了三个子表达式。第一个表达式包含月份,第二个包含某月中的某天,以及第三个包含年份。 现在我们就可以构建_replacement_,如下所示:

    \3-\1-\2
    

    此表达式给出了年份,一个斜杠,月份,一个斜杠,和某天。

    sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/' distros.txt
    

    我们还有两个问题。第一个是在我们表达式中额外的斜杠将会迷惑 sed,当 sed 试图解释这个 s 命令 的时候。第二个是因为 sed,默认情况下,只接受基本的正则表达式,在表达式中的几个字符会 被当作文字字面值,而不是元字符。我们能够解决这两个问题,通过反斜杠的自由应用来转义 令人不快的字符:

    sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt
    

    你掌握了吧!

    s 命令的另一个功能是使用可选标志,其跟随替代字符串。一个最重要的可选标志是 g 标志,其 指示 sed 对某个文本行全范围地执行查找和替代操作,不仅仅是对第一个实例,这是默认行为。 这里有个例子:

    [me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/'
    aaaBbbccc
    

    我们看到虽然执行了替换操作,但是只针对第一个字母 “b” 实例,然而剩余的实例没有更改。通过添加 g 标志, 我们能够更改所有的实例:

    [me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/g'
    aaaBBBccc
    

    目前为止,通过命令行我们只让 sed 执行单个命令。使用-f 选项,也有可能在一个脚本文件中构建更加复杂的命令。 为了演示,我们将使用 sed 和 distros.txt 文件来生成一个报告。我们的报告以开头标题,修改过的日期,以及 大写的发行版名称为特征。为此,我们需要编写一个脚本,所以我们将打开文本编辑器,然后输入以下文字:

    # sed script to produce Linux distributions report
    1 i\
    \
    Linux Distributions Report\
    s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/
    y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
    

    我们将把 sed 脚本保存为 distros.sed 文件,然后像这样运行它:

    [me@linuxbox ~]$ sed -f distros.sed distros.txt
    Linux Distributions Report
    SUSE	10.2	2006-12-07
    FEDORA	10	    2008-11-25
    SUSE	11.0	2008-06-19
    UBUNTU	8.04	2008-04-24
    FEDORA	8	    2007-11-08
    SUSE	10.3	2007-10-04
    UBUNTU	6.10	2006-10-26
    FEDORA	7	    2007-05-31
    UBUNTU	7.10	2007-10-18
    UBUNTU	7.04	2007-04-19
    SUSE	10.1	2006-05-11
    FEDORA	6	    2006-10-24
    FEDORA	9	    2008-05-13
    

    正如我们所见,我们的脚本文件产生了期望的结果,但是它是如何做到的呢?让我们再看一下我们的脚本文件。 我们将使用 cat 来给每行文本编号:

    [me@linuxbox ~]$ cat -n distros.sed
    1 # sed script to produce Linux distributions report
    2
    3 1 i\
    4 \
    5 Linux Distributions Report\
    6
    7 s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/
    8 y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
    

    我们脚本文件的第一行是一条注释。如同 Linux 系统中的许多配置文件和编程语言一样,注释以#字符开始, 然后是人类可读的文本。注释可以被放到脚本中的任意地方(虽然不在命令本身之中),且对任何 可能需要理解和/或维护脚本的人们都很有帮助。

    第二行是一个空行。正如注释一样,添加空白行是为了提高程序的可读性。

    许多 sed 命令支持行地址。这些行地址被用来指定对输入文本的哪一行执行操作。行地址可能被 表示为单独的行号,行号范围,以及特殊的行号“$”,它表示输入文本的最后一行。

    从第三行到第六行所包含地文本要被插入到地址 1 处,也就是输入文本的第一行中。这个 i 命令 之后是反斜杠回车符,来产生一个转义的回车符,或者就是所谓的连行符。这个序列能够 被用在许多环境下,包括 shell 脚本,从而允许把回车符嵌入到文本流中,而没有通知 解释器(在这是指 sed 解释器)已经到达了文本行的末尾。这个 i 命令,同样地,命令 a(追加文本, 而不是插入文本)和 c(取代文本)命令都允许多个文本行,只要每个文本行,除了最后一行,以一个 连行符结束。实际上,脚本的第六行是插入文本的末尾,它以一个普通的回车符结尾,而不是一个 连行符,通知解释器 i 命令结束了。


    注意:一个连行符由一个斜杠字符其后紧跟一个回车符组成。它们之间不允许有空白字符。


    第七行是我们的查找和替代命令。因为命令之前没有添加地址,所以输入流中的每一行文本 都得服从它的操作。

    第八行执行小写字母到大写字母的字符替换操作。注意不同于 tr 命令,这个 sed 中的 y 命令不 支持字符区域(例如,[a-z]),也不支持 POSIX 字符集。再说一次,因为 y 命令之前不带地址, 所以它会操作输入流的每一行。

    喜欢 sed 的人们也会喜欢。。。

    sed 是一款非常强大的程序,它能够针对文本流完成相当复杂的编辑任务。它最常 用于简单的行任务,而不是长长的脚本。许多用户喜欢使用其它工具,来执行较大的工作。 在这些工具中最著名的是 awk 和 perl。它们不仅仅是工具,像这里介绍的程序,且延伸到 完整的编程语言领域。特别是 perl,经常被用来代替 shell 脚本,来完成许多系统管理任务, 同时它也是一款非常流行网络开发语言。awk 更专用一些。其具体优点是其操作表格数据的能力。 awk 程序通常逐行处理文本文件,这点类似于 sed,awk 使用了一种方案,其与 sed 中地址 之后跟随编辑命令的概念相似。虽然关于 awk 和 perl 的内容都超出了本书所讨论的范围, 但是对于 Linux 命令行用户来说,它们都是非常好的技能。

    aspell

    我们要查看的最后一个工具是 aspell,一款交互式的拼写检查器。这个 aspell 程序是早先 ispell 程序 的继承者,大多数情况下,它可以被用做一个替代品。虽然 aspell 程序大多被其它需要拼写检查能力的 程序使用,但它也可以作为一个独立的命令行工具使用。它能够智能地检查各种类型的文本文件, 包括 HTML 文件,C/C++ 程序,电子邮件和其它种类的专业文本。

    拼写检查一个包含简单的文本文件,可以这样使用 aspell:

    aspell check textfile
    

    这里的 textfile 是要检查的文件名。作为一个实际例子,让我们创建一个简单的文本文件,叫做 foo.txt, 包含一些故意的拼写错误:

    [me@linuxbox ~]$ cat > foo.txt
    The quick brown fox jimped over the laxy dog.
    

    下一步我们将使用 aspell 来检查文件:

    [me@linuxbox ~]$ aspell check foo.txt
    

    因为 aspell 在检查模式下是交互的,我们将看到像这样的一个屏幕:

    The quick brown fox jimped over the laxy dog.
    1)jumped                        6)wimped
    2)gimped                        7)camped
    3)comped                        8)humped
    4)limped                        9)impede
    5)pimped                        0)umped
    i)Ignore                        I)Ignore all
    r)Replace                       R)Replace all
    a)Add                           l)Add Lower
    b)Abort                         x)Exit
    ?
    

    在显示屏的顶部,我们看到我们的文本中有一个拼写可疑且高亮显示的单词。在中间部分,我们看到 十个拼写建议,序号从 0 到 9,然后是一系列其它可能的操作。最后,在最底部,我们看到一个提示符, 准备接受我们的选择。

    如果我们按下 1 按键,aspell 会用单词 “jumped” 代替错误单词,然后移动到下一个拼写错的单词,就是 “laxy”。如果我们选择替代物 “lazy”,aspell 会替换 “laxy” 并且终止。一旦 aspell 结束操作,我们 可以检查我们的文件,会看到拼写错误的单词已经更正了。

    [me@linuxbox ~]$ cat foo.txt
    The quick brown fox jumped over the lazy dog.
    

    除非由命令行选项 --dont-backup 告诉 aspell,否则通过追加扩展名.bak 到文件名中, aspell 会创建一个包含原始文本的备份文件。

    为了炫耀 sed 的编辑本领,我们将还原拼写错误,从而能够重用我们的文件:

    [me@linuxbox ~]$ sed -i 's/lazy/laxy/; s/jumped/jimped/' foo.txt
    

    这个 sed 选项-i,告诉 sed 在适当位置编辑文件,意思是不要把编辑结果发送到标准输出中。sed 会把更改应用到文件中, 以此重新编写文件。我们也看到可以把多个 sed 编辑命令放在同一行,编辑命令之间由分号分隔开来。

    下一步,我们将看一下 aspell 怎样来解决不同种类的文本文件。使用一个文本编辑器,例如 vim(胆大的人可能想用 sed), 我们将添加一些 HTML 标志到文件中:

    <html>
        <head>
              <title>Mispelled HTML file</title>
        </head>
        <body>
              <p>The quick brown fox jimped over the laxy dog.</p>
        </body>
    </html>
    

    现在,如果我们试图拼写检查我们修改的文件,我们会遇到一个问题。如果我们这样做:

    [me@linuxbox ~]$ aspell check foo.txt
    

    我们会得到这些:

    <html>
        <head>
              <title>Mispelled HTML file</title>
        </head>
        <body>
              <p>The quick brown fox jimped over the laxy dog.</p>
        </body>
    </html>
    1) HTML                     4) Hamel
    2) ht ml                    5) Hamil
    3) ht-ml                    6) hotel
    i) Ignore                   I) Ignore all
    r) Replace                  R) Replace all
    a) Add                      l) Add Lower
    b) Abort                    x) Exit
    ?
    

    aspell 会认为 HTML 标志的内容是拼写错误。通过包含-H(HTML)检查模式选项,这个问题能够 解决,像这样:

    [me@linuxbox ~]$ aspell -H check foo.txt
    

    这会导致这样的结果:

    <html>
        <head>
              <title><b>Mispelled</b> HTML file</title>
        </head>
        <body>
              <p>The quick brown fox jimped over the laxy dog.</p>
        </body>
    </html>
    1) HTML                     4) Hamel
    2) ht ml                    5) Hamil
    3) ht-ml                    6) hotel
    i) Ignore                   I) Ignore all
    r) Replace                  R) Replace all
    a) Add                      l) Add Lower
    b) Abort                    x) Exit
    ?
    

    这个 HTML 标志被忽略了,并且只会检查文件中非标志部分的内容。在这种模式下,HTML 标志的 内容被忽略了,不会进行拼写检查。然而,ALT 标志的内容,会被检查。


    注意:默认情况下,aspell 会忽略文本中 URL 和电子邮件地址。通过命令行选项,可以重写此行为。 也有可能指定哪些标志进行检查及跳过。详细内容查看 aspell 命令手册。


    总结归纳

    在这一章中,我们已经查看了一些操作文本的命令行工具。在下一章中,我们会再看几个命令行工具。 诚然,看起来不能立即显现出怎样或为什么你可能使用这些工具为日常的基本工具, 虽然我们已经展示了一些半实际的命令用法的例子。我们将在随后的章节中发现这些工具组成 了解决实际问题的基本工具箱。这将是确定无疑的,当我们学习 shell 脚本的时候, 到时候这些工具将真正体现出它们的价值。

    拓展阅读

    GNU 项目网站包含了本章中所讨论工具的许多在线指南。

    友情提示

    有一些更有趣的文本操作命令值得。在它们之间有:split(把文件分割成碎片), csplit(基于上下文把文件分割成碎片),和 sdiff(并排合并文件差异)。

    更多相关内容
  • VsCode 中 vue代码格式化 (整理代码)

    千次阅读 2022-02-28 15:59:25
    ●在settings.json文件中,然后切换到文本格式,最后拷贝下面的内容 { // vscode默认启用了根据文件类型自动设置tabsize的选项 "editor.detectIndentation": false, // 重新设定tabsize "editor.tabSize": 2, /

    ●VS_Code中安装好插件 : Vetur
    在这里插入图片描述

    ●打开vscode --> 文件 --> 首选项 --> 设置

    ●选择扩展项 --> 打开settings.json文件
    在这里插入图片描述

    ●在settings.json文件中,然后切换到文本格式,最后拷贝下面的内容

    {
      "workbench.colorTheme": "Default Light+",
      "[html]": {
        "editor.defaultFormatter": "vscode.html-language-features"
      },
      // vscode默认启用了根据文件类型自动设置tabsize的选项
      "editor.detectIndentation": false,
      // 重新设定tabsize
      "editor.tabSize": 2,
      // #每次保存的时候自动格式化
      "editor.formatOnSave": true,
      // #每次保存的时候将代码按eslint格式进行修复
      "editor.codeActionsOnSave": {
        "source.fixAll": true
      },
      // 添加 vue 支持
      "eslint.validate": [
        "javascript",
        "vue",
        "html"
      ],
      // #让prettier使用eslint的代码格式进行校验
      "prettier.eslintIntegration": true,
      // #去掉代码结尾的分号
      "prettier.semi": false,
      // #使用带引号替代双引号
      "prettier.singleQuote": true,
      // #让函数(名)和后面的括号之间加个空格
      "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
      // #这个按用户自身习惯选择
      "vetur.format.defaultFormatter.html": "js-beautify-html",
      // #让vue中的js按编辑器自带的ts格式进行格式化
      "vetur.format.defaultFormatter.js": "vscode-typescript",
      "vetur.format.defaultFormatterOptions": {
        "js-beautify-html": {
          "wrap_attributes": "force-aligned"
          // #vue组件中html代码格式化样式
        }
      },
      // 格式化stylus, 需安装Manta's Stylus Supremacy插件
      "stylusSupremacy.insertColons": false, // 是否插入冒号
      "stylusSupremacy.insertSemicolons": false, // 是否插入分好
      "stylusSupremacy.insertBraces": false, // 是否插入大括号
      "stylusSupremacy.insertNewLineAroundImports": false, // import之后是否换行
      "stylusSupremacy.insertNewLineAroundBlocks": false, // 两个选择器中是否换行
      // html颜色高亮
      "files.associations": {
        ".eslintrc": "json",
        "*.vue": "html"
      },
      "emmet.syntaxProfiles": {
        "javascript": "jsx",
        "vue": "html",
        "vue-html": "html"
      }
    }
    

    在这里插入图片描述

    展开全文
  • 前一篇文章讲述了分类算法的原理知识级案例,包括决策树、KNN、SVM,并通过详细的分类对比实验和可视边界分析与大家总结。本文将详细讲解数据预处理、Jieba分词和文本聚类知识,这篇文章可以说是文本挖掘和自然...

    欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解,真心想把自己近十年的编程经验分享给大家,希望对您有所帮助,文章中不足之处也请海涵。Python系列整体框架包括基础语法10篇、网络爬虫30篇、可视化分析10篇、机器学习20篇、大数据分析20篇、图像识别30篇、人工智能40篇、Python安全20篇、其他技巧10篇。您的关注、点赞和转发就是对秀璋最大的支持,知识无价人有情,希望我们都能在人生路上开心快乐、共同成长。

    前一篇文章讲述了分类算法的原理知识级案例,包括决策树、KNN、SVM,并通过详细的分类对比实验和可视化边界分析与大家总结。本文将详细讲解数据预处理、Jieba分词和文本聚类知识,这篇文章可以说是文本挖掘和自然语言处理的入门文章。两万字基础文章,希望对您有所帮助。

    下载地址:

    前文赏析:

    第一部分 基础语法

    第二部分 网络爬虫

    第三部分 数据分析和机器学习

    作者新开的“娜璋AI安全之家”将专注于Python和安全技术,主要分享Web渗透、系统安全、人工智能、大数据分析、图像识别、恶意代码检测、CVE复现、威胁情报分析等文章。虽然作者是一名技术小白,但会保证每一篇文章都会很用心地撰写,希望这些基础性文章对你有所帮助,在Python和安全路上与大家一起进步。


    前文第一部分详细介绍了各种Python网络数据爬取方法,但所爬取的语料都是中文知识,在第二部分前面的章节也讲述了常用的数据分析模型及实例。这些实例都是针对数组或矩阵语料进行分析的,那么如何对中文文本语料进行数据分析呢?在本章作者将带领大家走进文本聚类分析领域,讲解文本预处理和文本聚类等实例内容。

    一.数据预处理概述

    在数据分析和数据挖掘中,通常需要经历前期准备、数据爬取、数据预处理、数据分析、数据可视化、评估分析等步骤,而数据分析之前的工作几乎要花费数据工程师近一半的工作时间,其中的数据预处理也将直接影响后续模型分析的好坏。

    数据预处理(Data Preprocessing)是指在进行数据分析之前,对数据进行的一些初步处理,包括缺失值填写、噪声处理、不一致数据修正、中文分词等,其目标是得到更标准、高质量的数据,纠正错误异常数据,从而提升分析的结果。

    图1是数据预处理的基本步骤,包括中文分词、词性标注、数据清洗、特征提取(向量空间模型存储)、权重计算(TF-IDF)等。

    在这里插入图片描述

    1.中文分词技术及Jieba工具
    在得到语料之后,首先需要做的就是对中文语料进行分词。由于中文词语之间是紧密联系的,一个汉语句子是由一串前后连续的汉字组成,词与词之间没有明显的分界标志,所以需要通过一定的分词技术把句子分割成空格连接的词序列。本章介绍了中文常用的分词技术,同时着重讲解了Python常用分词工具Jieba进行分词的实例。

    2.数据清洗及停用词过滤
    在使用Jieba中文分词技术得到分完词的语料后,可能会存在脏数据和停用词等现象。为了得到更好的数据分析结果,需要对这些数据集进行数据清洗和停用词过滤等操作,这里利用Jieba库进行清洗数据。

    3.词性标注
    词性标注是指为分词结果中的每个单词或词组标注一个正确的词性,即确定每个词是名词、动词、形容词或其他词性的过程。通过词性标注可以确定词在上下文中的作用,通常词性标注是自然语言处理和数据预处理的基础步骤,Python也提供了相关库进行词性标注。

    在这里插入图片描述

    4.特征提取
    特征提取是指将原始特征转换为一组具有明显物理意义或者统计意义的核心特征,所提取的这组特征可以尽可能地表示这个原始语料,提取的特征通常会存储至向量空间模型中。向量空间模型是用向量来表征一个文本,它将中文文本转化为数值特征。本章介绍了特征提取、向量空间模型和余弦相似性的基本知识,同时结合实例进行深入讲解。

    5.权重计算及TFIDF
    在建立向量空间模型过程中,权重的表示尤为重要,常用方法包括布尔权重、词频权重、TF-IDF权重、熵权重方法等。本章讲述了常用的权重计算方法,并详细讲解了TF-IDF的计算方法和实例。

    现在假设存在表1所示的数据集,并存储至本地test.txt文件中,整章内容都将围绕该数据集进行讲解,数据集共分为9行数据,包括3个主题,即:贵州旅游、大数据和爱情。接下来依次对数据预处理的各个步骤进行分析讲解。

    在这里插入图片描述

    贵州省位于中国的西南地区,简称“黔”或“贵”。
    走遍神州大地,醉美多彩贵州。
    贵阳市是贵州省的省会,有“林城”之美誉。
    数据分析是数学与计算机科学相结合的产物。
    回归、聚类和分类算法被广泛应用于数据分析。
    数据爬取、数据存储和数据分析是紧密相关的过程。
    最甜美的是爱情,最苦涩的也是爱情。
    一只鸡蛋可以画无数次,一场爱情能吗?
    真爱往往珍藏于最平凡、普通的生活中。
    

    二.中文分词

    当读者使用Python爬取了中文数据集之后,首先需要对数据集进行中文分词处理。由于英文中的词与词之间是采用空格关联的,按照空格可以直接划分词组,所以不需要进行分词处理,而中文汉字之间是紧密相连的,并且存在语义,词与词之间没有明显的分隔点,所以需要借助中文分词技术将语料中的句子按空格分割,变成一段段词序列。下面开始详细介绍中文分词技术及Jiaba中文分词工具。

    1.中文分词技术

    中文分词(Chinese Word Segmentation)指将汉字序列切分成一个个单独的词或词串序列,它能够在没有词边界的中文字符串中建立分隔标志,通常采用空格分隔。中文分词是数据分析预处理、数据挖掘、文本挖掘、搜索引擎、知识图谱、自然语言处理等领域中非常基础的知识点,只有经过中文分词后的语料才能转换为数学向量的形式,继续进行后面的分析。同时,由于中文数据集涉及到语义、歧义等知识,划分难度较大,比英文复杂很多。下面举个简单示例,对句子“我是程序员”进行分词操作。

    输入:我是程序员
    输出1:我\是\程\序\员
    输出2:我是\是程\程序\序员
    输出3:我\是\程序员
    

    这里分别采用了三种方法介绍中文分词。

    • “我\是\程\序\员”采用的是一元分词法,将中文字符串分隔为单个汉字;
    • “我是\是程\程序\序员”采用二元分词法,将中文汉字两两分隔;
    • “我\是\程序员”是比较复杂但更实用的分词方法,它根据中文语义来进行分词的,其分词结果更准确。

    中文分词方法有很多,常见的包括:

    • 基于字符串匹配的分词方法
    • 基于统计的分词方法
    • 基于语义的分词方法

    这里介绍比较经典的基于字符串匹配的分词方法。

    基于字符串匹配的分词方法又称为基于字典的分词方法,它按照一定策略将待分析的中文字符串与机器词典中的词条进行匹配,若在词典中找到某个字符串,则匹配成功,并识别出对应的词语。该方法的匹配原则包括最大匹配法(MM)、逆向最大匹配法(RMM)、逐词遍历法、最佳匹配法(OM)、并行分词法等。

    正向最大匹配法的步骤如下,假设自动分词词典中的最长词条所含汉字的个数为n。

    • ① 从被处理文本中选取当前中文字符串中的前n个中文汉字作为匹配字段,查找分词词典,若词典中存在这样一个n字词,则匹配成功,匹配字段作为一个词被切分出来。
    • ② 若分词词典中找不到这样的一个n字词,则匹配失败,匹配字段去掉最后一个汉字,剩下的中文字符作为新的匹配字段,继续进行匹配。
    • ③ 循环步骤进行匹配,直到匹配成功为止。

    例如,现在存在一个句子“北京理工大学生前来应聘”,使用正向最大匹配方法进行中文分词的过程如下所示。

    分词算法:正向最大匹配法
    输入字符:北京理工大学生前来应聘
    分词词典:北京、北京理工、理工、大学、大学生、生前、前来、应聘
    最大长度:6
    

    匹配过程:

    • (1)选取最大长度为6的字段匹配,即“北京理工大学”匹配词典“北京理工大学”在词典中没有匹配字段,则去除一个汉字,剩余“北京理工大”继续匹配,该词也没有匹配字段,继续去除一个汉字,即“北京理工”,分词词典中存在该词,则匹配成功。结果:匹配“北京理工”

    • (2)接着选取长度为6的字符串进行匹配,即“大学生前来应” “大学生前来应”在词典中没有匹配字段,继续从后去除汉字,“大学生” 三个汉字在词典中匹配成功。结果:匹配“大学生”

    • (3)剩余字符串“前来应聘”继续匹配“前来应聘”在词典中没有匹配字段,继续从后去除汉字,直到“前来”。结果:匹配“前来”

    • (4)最后的字符串“应聘”进行匹配。结果:匹配“应聘”

    • 分词结果:北京理工 \ 大学生 \ 前来 \ 应聘

    随着中文数据分析越来越流行、应用越来越广,针对其语义特点也开发出了各种各样的中文分词工具,常见的分词工具包括:

    • Stanford汉语分词工具
    • 哈工大语言云(LTP -cloud)
    • 中国科学院汉语词法分析系统(ICTCLAS)
    • IKAnalyzer分词
    • 盘古分词
    • 庖丁解牛分词

    同时针对Python语言的常见中文分词工具包括:盘古分词、Yaha分词、Jieba分词等,它们的用法都相差不大,由于结巴分词速度较快,可以导入词典如“颐和园”、“黄果树瀑布”等专有名词再进行中文分词等特点,本文主要介绍结巴(Jieba)分词工具讲解中文分词。


    2.Jieba中文分词用法

    (1) 安装过程
    作者推荐大家使用PIP工具来安装Jieba中文分词包。安装语句如下:

    pip install jieba
    

    调用命令“pip install jieba”安装jieba中文分词包如图所示。

    在这里插入图片描述

    安装过程中的会显示安装配置相关包和文件的百分比,直到出现“Successfully installed jieba”命令,表示安装成功。注意,在安装过程中会遇到各种问题,大家一定要学会独立搜索答案解决这些问题,才能提升您独立解决问题的能力。

    同时,如果您使用Anaconda Spyder集成环境,则调用“Anaconda Prompt”命令行模式输入“pip install jieba”命令进行安装。如果您的Python开发环境已经安装了该扩展包,则会提示已经存在Jieba中文分词包,如图所示。

    在这里插入图片描述


    (2) 基础用法
    首先读者看一段简单的结巴分词代码。

    • jieba.cut(text,cut_all=True)
      分词函数,第一个参数是需要分词的字符串,第二个参数表示是否为全模式。分词返回的结果是一个可迭代的生成器(generator),可使用for循环来获取分词后的每个词语,更推荐读者转换为list列表再使用。
    • jieba.cut_for_search(text)
      搜索引擎模式分词,参数为分词的字符串,该方法适合用于搜索引擎构造倒排索引的分词,粒度比较细。
    #coding=utf-8
    #By:Eastmount CSDN
    import jieba  
      
    text = "小杨毕业于北京理工大学,从事Python人工智能相关工作。"  
    
    #全模式
    data = jieba.cut(text,cut_all=True)
    print(type(data))
    print(u"[全模式]: ", "/".join(data))
    
    #精确模式  
    data = jieba.cut(text,cut_all=False)
    print(u"[精确模式]: ", "/".join(data))
    
    #默认是精确模式 
    data = jieba.cut(text)  
    print(u"[默认模式]: ", "/".join(data))
    
    #搜索引擎模式 
    data = jieba.cut_for_search(text)    
    print(u"[搜索引擎模式]: ", "/".join(data))
    
    #返回列表
    seg_list = jieba.lcut(text, cut_all=False)
    print("[返回列表]: {0}".format(seg_list))
    

    输出结果如下所示。

    在这里插入图片描述

    最终的分词结果比较理想,其中精确模式输出的“小/杨/毕业/于/北京理工大学/,/从事/Python/人工智能/相关/工作/。”比较精准。下面简单叙述结巴中文分词的三种分词模式。

    全模式
    该模式将语料中所有可以组合成词的词语都构建出来,其优点是速度非常快,缺点是不能解决歧义问题,并且分词结果不太准确。其分词结果为“小/杨/毕业/于/北京/北京理工/北京理工大学/理工/理工大/理工大学/工大/大学///从事/Python/人工/人工智能/智能/相关/工作//”。

    精确模式
    该模式利用其算法将句子最精确地分隔开,适合文本分析,通常采用这种模式进行中文分词。其分词结果为“小/杨/毕业/于/北京理工大学/,/从事/Python/人工智能/相关/工作/。”,其中“北京理工大学”、“人工智能”这些完整的名词被精准识别,但也有部分词未被识别,后续导入词典可以实现专有词汇识别。

    搜索引擎模式
    该模式是在精确模式基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。其结果为“小/杨/毕业/于/北京/理工/工大/大学/理工大/北京理工大学/,/从事/Python/人工/智能/人工智能/相关/工作/。”。

    Python提供的结巴(Jieba)中文分词包主要利用基于Trie树结构实现高效的词图扫描(构建有向无环图DAG)、动态规划查找最大概率路径(找出基于词频的最大切分组合)、基于汉字成词能力的HMM模型等算法,这里不进行详细叙述,本书更侧重于应用案例。同时结巴分词支持繁体分词和自定义字典方法。

    • ==load_userdict(f) ==

    (3) 中文分词实例
    下面对表1中的语料进行中文分词。代码为依次读取文件中的内容,并调用结巴分词包进行中文分词,然后存储至本地文件中。

    #coding=utf-8
    #By:Eastmount CSDN
    import os  
    import codecs
    import jieba  
    import jieba.analyse
    
    source = open("test.txt", 'r')
    line = source.readline().rstrip('\n')
    content = []
    while line!="":
        seglist = jieba.cut(line,cut_all=False)  #精确模式  
        output = ' '.join(list(seglist))         #空格拼接  
        print(output)
        content.append(output)
        line = source.readline().rstrip('\n')
    else:
        source.close() 
    

    输出如图所示,可以看到分词后的语料。

    在这里插入图片描述


    三.数据清洗

    在分析语料的过程中,通常会存在一些脏数据或噪声词组干扰我们的实验结果,这就需要对分词后的语料进行数据清洗(Data Cleaning)。比如前面使用Jieba工具进行中文分词,它可能存在一些脏数据或停用词,如“我们”、“的”、“吗”等。这些词降低了数据质量,为了得到更好的分析结果,需要对数据集进行数据清洗或停用词过滤等操作。本节主要介绍数据清洗概念、中文数据清洗技术及停用词过滤,并利用Jieba分词工具进行停用词和标点符号的清洗。

    1.数据清洗概述

    脏数据通常是指数据质量不高、不一致或不准确的数据,以及人为造成的错误数据等。作者将常见的脏数据分为四类:

    • 残缺数据
      该类数据是指信息存在缺失的数据,通常需要补齐数据集再写入数据库或文件中。比如统计9月份30天的销售数据,但期间某几天数据出现丢失,此时需要对数据进行补全操作。
    • 重复数据
      数据集中可能存在重复数据,此时需要将重复数据导出让客户确认并修正数据,从而保证数据的准确性。在清洗转换阶段,对于重复数据项尽量不要轻易做出删除决策,尤其不能将重要的或有业务意义的数据过滤掉,校验和重复确认的工作是必不可少的。
    • 错误数据
      该类脏数据常常出现在网站数据库中,是指由于业务系统不够健全,在接收输入后没有进行判断或错误操作直接写入后台数据库造成的,比如字符串数据后紧跟一个回车符、不正确的日期格式等。这类错误可以通过去业务系统数据库用SQL语句进行挑选,再交给业务部门修正。
    • 停用词
      分词后的语料并不是所有的词都与文档内容相关,往往存在一些表意能力很差的辅助性词语,比如中文词组“我们”、“的”、“可以”等,英文词汇“a”、“the”等。这类词在自然语言处理或数据挖掘中被称为停用词(Stop Words),它们是需要进行过滤的。通常借用停用词表或停用词字典进行过滤。

    数据清洗主要解决脏数据,从而提升数据质量,它主要应用于数据仓库、数据挖掘、数据质量管理等领域。读者可以简单将数据清洗定位为:只要是有助于解决数据质量问题的处理过程就被认为是数据清洗,不同领域的数据清洗定义有所不同。总之,数据清洗的目的是保证数据质量,提供准确数据,其任务是通过过滤或者修改那些不符合要求的数据,从而更好地为后面的数据分析作铺垫。

    在这里插入图片描述

    为了解决上述问题,将数据清洗方法划分为:

    • 解决残缺数据
      对于空值或缺失数据,需要采用估算填充方法解决,常见的估算方法包括样本均值、中位数、众数、最大值、最小值等填充,比如选取所有数据的平均值来填充缺失数据。这些方法会存在一定的误差,如果空值数据较多,则会对结果造成影响,偏离实际情况。
    • 解决重复数据
      简单的重复数据需要人为识别,而计算机解决重复数据的方法较为复杂。其方法通常会涉及到实体识别技术,采用有效的技术识别处相似的数据,这些相似数据指向同一实体,再对这些重复数据进行修正。
    • 解决错误数据
      对于错误数据,通常采用统计方法进行识别,如偏差分析、回归方程、正态分布等,也可以用简单的规则库检测数值范围,使用属性间的约束关系来校对这些数据。
    • 解决停用词
      停用词概念由Hans Peter Luhn提出,并为信息处理做出了巨大的贡献。通常存在一个存放停用词的集合,叫做停用词表,停用词往往由人工根据经验知识加入,具有通用性。解决停用词的方法即利用停用词词典或停用词表进行过滤。比如“并”、“当”、“地”、“啊”等字都没有具体的含义,需要过滤,还存在一些如“我们”、“但是”、“别说”、“而且”等词组也需要过滤。

    2.中文语料清洗

    前面已将Python爬取的中文文本语料进行了分词处理,接下来 需要对其进行数据清洗操作,通常包括停用词过滤和特殊标点符号去除等,而对于空值数据、重复数据,作者更建议大家在数据爬取过程中就进行简单的判断或补充缺失值。下面对表1所提供的中文语料(包括贵州、大数据和爱情三个主题)进行数据清洗实例操作。

    (1) 停用词过滤
    上图是使用结巴工具中文分词后的结果,但它存在一些出现频率高却不影响文本主题的停用词,比如“数据分析是数学与计算机科学相结合的产物”句子中的“是”、“与”、“的”等词,这些词在预处理时是需要进行过滤的。

    这里作者定义一个符合该数据集的常用停用词表的数组,然后将分词后的序列,每一个字或词组与停用词表进行比对,如果重复则删除该词语,最后保留的文本能尽可能地反应每行语料的主题。代码如下:

    #coding=utf-8
    #By:Eastmount CSDN
    import os  
    import codecs
    import jieba  
    import jieba.analyse
    
    #停用词表
    stopwords = {}.fromkeys(['的', '或', '等', '是', '有', '之', '与',
                             '和', '也', '被', '吗', '于', '中', '最'])
    
    source = open("test.txt", 'r')
    line = source.readline().rstrip('\n')
    content = []                                 #完整文本
    
    while line!="":
        seglist = jieba.cut(line,cut_all=False)  #精确模式
        final = []                               #存储去除停用词内容
        for seg in seglist: 
            if seg not in stopwords:  
                final.append(seg)
        output = ' '.join(list(final))           #空格拼接
        print(output)
        content.append(output)
        line = source.readline().rstrip('\n')
    else:
        source.close()
    

    其中stopwords变量定义了停用词表,这里只列举了与我们test.txt语料相关的常用停用词,而在真实的预处理中,通常会从文件中导入常见的停用词表,包含了各式各样的停用词,读者可以去网上搜索查看。

    核心代码是for循环判断分词后的语料是否在停用词表中,如果不在则添加到新的数组final中,最后保留的就是过滤后文本,如图所示。

    在这里插入图片描述


    (2) 去除标点符号
    在做文本分析时,标点符号通常也会被算成一个特征,从而影响分析的结果,所以我们需要把标点符号也进行过滤。其过滤方法和前面过滤停用词的方法一致,建立一个标点符号的数组或放到停用词stopwords中,停用词数组如下:

    stopwords = {}.fromkeys(['的', '或', '等', '是', '有', '之', '与',
                             '和', '也', '被', '吗', '于', '中', '最',
                             '“', '”', '。', ',', '?', '、', ';'])
    

    同时将文本内容存储至本地result.txt文件中,完整代码如下:

    # coding=utf-8
    #By:Eastmount CSDN
    import os  
    import codecs
    import jieba  
    import jieba.analyse
    
    #停用词表
    stopwords = {}.fromkeys(['的', '或', '等', '是', '有', '之', '与',
                             '和', '也', '被', '吗', '于', '中', '最',
                             '“', '”', '。', ',', '?', '、', ';'])
    
    source = open("test.txt", 'r')
    result = codecs.open("result.txt", 'w', 'utf-8')
    line = source.readline().rstrip('\n')
    content = []                                 #完整文本
    
    while line!="":
        seglist = jieba.cut(line,cut_all=False)  #精确模式
        final = []                               #存储去除停用词内容
        for seg in seglist:   
            if seg not in stopwords:  
                final.append(seg)
        output = ' '.join(list(final))           #空格拼接
        print(output)
        content.append(output)
        result.write(output + '\r\n')
        line = source.readline().rstrip('\n')
    else:
        source.close()
        result.close()
    

    输出结果如图7所示,得到的语料非常精炼,尽可能的反应了文本主题,其中1-3行为贵州旅游主题、4-6为大数据主题、7-9位爱情主题。

    在这里插入图片描述


    四.特征提取及向量空间模型

    本小节主要介绍特征提取、向量空间模型和余弦相似性的基础知识,并用表21.1所提供的语料进行基于向量空间模型的余弦相似度计算。

    1.特征规约

    经过网络爬取、中文分词、数据清洗后的语料通常称为初始特征集,而初始特征集通常都是由高维数据组成,并且不是所有的特征都很重要。高维数据中可能包含不相关的信息,这会降低算法的性能,甚至高维数据会造成维数灾难,影响数据分析的结果。

    研究发现,减少数据的冗余维度(弱相关维度)或提取更有价值的特征能够有效地加快计算速度,提高效率,也能够确保实验结果的准确性,学术上称为特征规约。

    特征规约是指选择与数据分析应用相关的特征,以获取最佳性能,并且处理的工作量更小。特征规约包含两个任务:特征选择和特征提取。它们都是从原始特征中找出最有效的特征,并且这些特征能尽可能地表征原始数据集。

    (1) 特征提取
    特征提取是将原始特征转换为一组具有明显物理意义或者统计意义的核心特征,所提取的这组特征可以尽可能地表示这个原始语料。特征提取分为线性特征提取和非线性特征提取,其中线性特征提取常见的方法包括:

    • PCA主成分分析方法。该方法寻找表示数据分布的最优子空间,将原始数据降维并提取不相关的部分,常用于降维,参考前面聚类那篇文章。
    • LDA线性判别分析方法。该方法寻找可分性判据最大的子空间。
    • ICA独立成分分析方法。该方法将原始数据降维并提取出相互独立的属性,寻找一个线性变换。

    非线性特征提取常见方法包括Kernel PCA、Kernel FDA等。

    (2) 特征选择
    特征选择是从特征集合中挑选一组最具统计意义的特征,从而实现降维,通常包括产生过程、评价函数、停止准则、验证过程四个部分。传统方法包括信息增益(Information Gain,简称IG)法、随机产生序列选择算法、遗传算法( Genetic Algorithms,简称GA )等。

    下图是图像处理应用中提取Lena图的边缘线条特征的实例,可以利用一定量的特征尽可能的描述整个人的轮廓,它和数据分析中的应用也是相同的原理。

    在这里插入图片描述


    2.向量空间模型

    向量空间模型(Vector Space Model,简称VSM)是通过向量的形式来表征一个文档,它能够将中文文本转化为数值特征,从而进行数据分析。作为目前最为成熟和应用最广的文本表示模型之一,向量空间模型已经广泛应用于数据分析、自然语言处理、中文信息检索、数据挖掘、文本聚类等领域,并取得了一定成果。

    采用向量空间模型来表示一篇文本语料,它将一个文档(Document)或一篇网页语料(Web Dataset)转换为一系列的关键词(Key)或特征项(Term)的向量。

    • 特征项(Trem)
      特征项是指文档所表达的内容由它所含的基本语言单位(字、词、词组或短语)组成,在文本表示模型中,基本语言单位即称为文本的特征项。例如文本Doc中包含n个特征项,表示为:

    在这里插入图片描述

    • 特征权重(Trem Weight)
      特征权重是指为文档中的某个特征项ti(1≤ i ≤n)赋予权重wi,以表示该特征项对于文档内容的重要程度,权重越高的特征项越能反应其在文档中的重要性。文本Doc中存在n个特征项,即:{t1, t2, t3, … , tn-1, tn},它是一个n维坐标,接着需要计算出各特征项ti在文本中的权重wi,为对应特征的坐标值。按特征权重文本表示如下,其中,WDoc称为文本Doc的特征向量。

    在这里插入图片描述

    • 文档表示
      得到了特征项和特征权重后,需要表示一篇文档,则利用下面这个公式。其中,文档Doc共包含n个特征词和n个权重。ti是一系列相互之间不同的特征词,i=1,2,…,n。wi(d)是特征词ti在文档d中的权重,它通常可以被表达为ti在d中呈现的频率。

    在这里插入图片描述

    特征项权重W有很多种不同的计算方法,最简单的方法是以特征项在文本中的出现次数作为该特征项的权重,第五部分将详细叙述。

    在这里插入图片描述

    从上图可以看到,将文档存储为词频向量的过程,转换为{1,0,1,0,…,1,1,0}形式。特征项的选取和特征项权重的计算是向量空间模型的两个核心问题,为了使特征向量更能体现文本内容的含义,要为文本选择合理的特征项,并且在给特征项赋权重时遵循对文本内容特征影响越大的特征项的权值越大的原则。


    3.余弦相似度计算

    当使用上面的向量空间模型计算得到两篇文章的向量后,则可以计算两篇文章的相似程度,两篇文章间的相似度通过两个向量的余弦夹角Cos来描述。文本D1和D2的相似度计算公式如下:

    在这里插入图片描述

    其中,分子表示两个向量的点乘积,分母表示两个向量的模的乘积。通过余弦相似性计算后,得到了任意两篇文章的相似程度,可以将相似程度越高的文档归类到同一主题,也可以设定阈值进行聚类分析。该方法的原理是将语言问题转换为数学问题来解决实际问题。

    下图是向量空间模型图,它展示了文档Term1、Term2、…、TermN之间的余弦相似度计算方法,如果两篇文档越相似,则其夹角θ越小,Cos值越接近于1,当两篇文档完全相似时,此时的夹角为0°,Cos值为1。这也展示了余弦相似性的原理知识。

    在这里插入图片描述

    下面我们借用两个句子来计算其与“北京理工大学生前来应聘”的余弦相似程度。假设存在三个句子,需要看哪一个句子和“北京理工大学生前来应聘”相似程度更高,则认为主题更为类似。那么,如何计算句子A和句子B的相似性呢?

    句子1:北京理工大学生前来应聘
    
    句子2:清华大学大学生也前来应聘
    
    句子3: 我喜欢写代码
    

    下面采用向量空间模型、词频及余弦相似性计算句子2和句子3分别与句子1的相似性。

    第一步:中文分词。

    句子1:北京理工 / 大学生 / 前来 / 应聘
    
    句子2:清华大学 / 大学生 // 前来 / 应聘
    
    句子3: 我 / 喜欢 // 代码
    

    第二步:列出所有词语,按照词出现的先后顺序。

    北京理工 / 大学生 / 前来 / 应聘 / 清华大学 /// 喜欢 // 代码
    

    第三步:计算词频。如表所示。

    在这里插入图片描述

    第四步:写出词频向量。

    句子1[1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
    
    句子2[0, 1, 1, 1, 1, 1, 0, 0, 0, 0]
    
    句子3[0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
    

    第五步:计算余弦相似度。

    在这里插入图片描述

    在这里插入图片描述

    其结果显示句子1和句子2的相似度为0.67,存在一定的相似主题;而句子1和句子3的相似度为0,完全不相似。

    总之,余弦相似度是一种非常有用的算法,只要是计算两个向量的相似程度,都可用它。当余弦值越接近1时,表明两个向量的夹角越接近0度,两个向量越相似。但余弦相似性作为最简单的相似度计算方法,也存在一些缺点,如计算量太大、词之间的关联性没考虑等。


    五.权重计算

    前面讲述的词频权重计算的方法过于简单,下面就给大家介绍下其他权重计算方法。

    权重计算是指通过特征权重来衡量特征项在文档表示中的重要程度,给特征词赋予一定的权重来衡量统计文本特征词。常用的权重计算方法包括:布尔权重、绝对词频、倒文档词频、TF-IDF、TFC、熵权重等。

    1.常用权重计算方法

    (1) 布尔权重
    布尔权重是比较简单的权重计算方法,设定的权重要么是1,要么是0。如果在文本中出现了该特征词,则文本向量对应该特征词的分量赋值为1;如果该特征词没有在文本中出现,则分量为0。公式如下所示,其中wij表示特征词ti在文本Dj中的权重。

    在这里插入图片描述

    假设特征向量为:

    • {北京理工,大学生,前来,应聘,清华大学,也,我,喜欢,写,代码}

    现在需要计算句子“北京理工大学生前来应聘”的权重,则特征词在特征向量中存在的,对应分量为1,不存在的对应分量为0,最终特征向量结果为:

    • {1,1,1,1,0,0,0,0,0,0}

    但是实际应用中,布尔权重0-1值是无法体现特征词在文本中的重要程度,那就衍生出了词频这种方法。


    (2) 绝对词频
    词频方法又称为绝对词频(Term Frequency,简称TF),它首先计算特征词在文档中出现的频率,再来表征文本。通常使用tfij表示,即特征词ti在训练文本Dj中出现的频率。

    在这里插入图片描述

    假设句子为“北京理工大学的大学生和清华大学的大学生前来应聘”,而对应的特征词为:{北京理工,大学生,前来,应聘,清华大学,也,我,喜欢,写,代码,的,和},对应词频向量为:

    • {1,2,1,1,1,0,0,0,0,0,2,1}

    前面所采用的向量空间模型计算文本余弦相似性的例子也使用的是词频,这是权重计算方法中最简单、有效的方法之一。


    (3) 倒文档频率
    由于词频方法无法体现低频特征项的区分能力,往往存在某些特征项频率很高,却在文本中起到很低影响程度的现象,如“我们”、“但是”、“的”等词语;同时,有的特征项虽然出现的频率很低,但表达着整个文本的核心思想,起着至关重要的作用。

    倒文档频率(Inverse Document Frequency,简称IDF)方法是Spark Jones在1972年提出的,用于计算词与文献相关权重的经典方法。公式如下:

    在这里插入图片描述

    其中,参数|D|表示语料的文本总数,表示文本所包含特征词ti的数量。

    在倒文档频率方法中,权重是随着特征词的文档数量的变化呈反向变化。如某些常用词“我们”、“但是”、“的”等,在所有文档中出现频率很高,但它的IDF值却非常低。甚至如果它每篇文档都出现,则log1的计算结果为0,从而降低了这些常用词的作用;相反,如果某篇介绍“Python”的词,仅仅在该篇文档中出现,它的作用就非常高。

    同样还有很多权重计算方法,包括TF-IDF、熵权重、TF-IWF、基于错误驱动的特征权重算法等,读者可以自行研究,这里仅仅简单引入了最基础的几种方法。


    2.TF-IDF

    TF-IDF(Term Frequency-Invers Document Frequency)是近年来用于数据分析和信息处理经典的权重计算技术。该技术根据特征词在文本中出现的次数和在整个语料中出现的文档频率来计算该特征词在整个语料中的重要程度,其优点是能过滤掉一些常见却无关紧要的词语,尽可能多的保留影响程度高的特征词。

    其中,TF(Term Frequency)表示某个关键词在整篇文章中出现的频率或次数。IDF(Invers Document Frequency)表示倒文本频率,又称为逆文档频率,它是文档频率的倒数,主要用于降低所有文档中一些常见却对文档影响不大的词语的作用。TF-IDF的完整公式如下:

    在这里插入图片描述

    式中tfidfi,j表示词频tfi,j和倒文本词频idfi的乘积,TF-IDF中权重与特征项在文档中出现的频率成正比,与在整个语料中出现该特征项的文档数成反比。tfidfi,j值越大则该特征词对这个文本的重要程度越高。

    TF词频的计算公式如下:

    在这里插入图片描述

    其中,ni,j为特征词ti在训练文本Dj中出现的次数,是文本Dj中所有特征词的个数,计算的结果即为某个特征词的词频。

    TF-IDF公式推导如下所示:

    在这里插入图片描述

    TF-IDF技术的核心思想是如果某个特征词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来做权重计算。TF-IDF算法简单快速,结果也符合实际情况,其缺点是单纯以词频衡量一个词的重要性,不够全面,有时重要的词可能出现次数并不多,并且该算法无法体现词的位置信息。


    3.Sklearn计算TF-IDF

    Scikit-Learn是基于Python的机器学习模块,基本功能主要分为六个部分:分类、回归、聚类、数据降维、模型选择和数据预处理,具体可以参考官方网站上的文档。本书前面详细介绍了Scikit-Learn的安装及使用方法。这里主要使用Scikit-Learn中的两个类CountVectorizer和TfidfTransformer,用来计算词频和TF-IDF值。

    • CountVectorizer
      该类是将文本词转换为词频矩阵的形式。比如“I am a teacher”文本共包含四个单词,它们对应单词的词频均为1,“I”、“am”、“a”、“teacher”分别出现一次。CountVectorizer将生成一个矩阵a[M][N],共M个文本语料,N个单词,比如a[i][j]表示单词j在i类文本下的词频。再调用fit_transform()函数计算各个词语出现的次数,get_feature_names()函数获取词库中的所有文本关键词。

    计算result.txt文本的词频代码如下,下表是表1数据集被中文分词、数据清洗后的结果,如下所示。

    在这里插入图片描述

    贵州省 位于 中国 西南地区 简称 黔 贵
    走遍 神州大地 醉美 多彩 贵州
    贵阳市 是 贵州省 省会 有 林城 美誉
    数据分析 是 数学 计算机科学 相结合 产物 
    回归 聚类 分类 算法 广泛应用 数据分析
    数据 爬取 数据 存储 数据分析 紧密 相关 过程
    最 甜美 爱情 最 苦涩 爱情
    一只 鸡蛋 可以 画 无数次 一场 爱情
    真爱 往往 珍藏 最 平凡 普通 生活
    

    代码如下:

    #coding:utf-8
    #By:Eastmount CSDN
    from sklearn.feature_extraction.text import CountVectorizer  
      
    #存储读取语料 一行预料为一个文档
    corpus = []  
    for line in open('result.txt', 'r', encoding="utf-8").readlines():  
        corpus.append(line.strip())
        
    #将文本中的词语转换为词频矩阵  
    vectorizer = CountVectorizer()
    
    #计算个词语出现的次数  
    X = vectorizer.fit_transform(corpus)
    
    #获取词袋中所有文本关键词  
    word = vectorizer.get_feature_names()  
    for n in range(len(word)):  
        print(word[n],end=" ")
    print('')
        
    #查看词频结果  
    print(X.toarray())
    

    输出如下图所示。

    在这里插入图片描述

    • TfidTransformer
      当使用CountVectorizer类计算得到词频矩阵后,接下来通过TfidfTransformer类实现统计vectorizer变量中每个词语的TF-IDF值,代码补充如下。
    #coding:utf-8
    #By:Eastmount CSDN
    from sklearn.feature_extraction.text import CountVectorizer  
    from sklearn.feature_extraction.text import TfidfTransformer
    
    #存储读取语料
    corpus = []  
    for line in open('result.txt', 'r', encoding="utf-8").readlines():  
        corpus.append(line.strip())
    vectorizer = CountVectorizer()        #将文本中的词语转换为词频矩阵 
    X = vectorizer.fit_transform(corpus)  #计算个词语出现的次数  
    word = vectorizer.get_feature_names() #获取词袋中所有文本关键词   
    for n in range(len(word)):  
        print(word[n],end=" ")
    print('')  
    print(X.toarray())                    #查看词频结果 
    
    #计算TF-IDF值
    transformer = TfidfTransformer()  
    print(transformer)
    tfidf = transformer.fit_transform(X) #将词频矩阵X统计成TF-IDF值  
    #查看数据结构 
    print(tfidf.toarray())               #tfidf[i][j]表示i类文本中的tf-idf权重
    

    运行部分结果如下图所示。

    在这里插入图片描述

    TF-IDF值采用矩阵数组的形式存储,每一行数据代表一个文本语料,每一行的每一列都代表其中一个特征对应的权重,得到TF-IDF后就可以运用各种数据分析算法进行分析,比如聚类分析、LDA主题分布、舆情分析等等。


    六.文本聚类

    获取文本TF-IDF值之后,本小节简单讲解使用TF-IDF值进行文本聚类的过程,主要包括如下五个步骤:

    • 第一步,对中文分词和数据清洗后的语料进行词频矩阵生成操作。主要调用CountVectorizer类计算词频矩阵,生成的矩阵为X。
    • 第二步,调用TfidfTransformer类计算词频矩阵X的TF-IDF值,得到Weight权重矩阵。
    • 第三步,调用Sklearn机器学习包的KMeans类执行聚类操作,设置的类簇数n_clusters为3,对应语料贵州、数据分析和爱情的三个主题。然后调用fit()函数训练,并将预测的类标赋值给y_pred数组。
    • 第四步,调用Sklearn库PCA()函数进行降维操作。由于TF-IDF是多维数组,是9行文本所有特征对应的权重,而在绘图之前需要将这些特征降低为二维,对应X和Y轴。
    • 第五步,调用Matplotlib函数进行可视化操作,绘制聚类图形,并设置图形参数、标题、坐标轴内容等。

    代码如下。

    # coding:utf-8
    #By:Eastmount CSDN
    from sklearn.feature_extraction.text import CountVectorizer
    from sklearn.feature_extraction.text import TfidfTransformer  
      
    #第一步 生成词频矩阵
    corpus = []  
    for line in open('result.txt', 'r', encoding="utf-8").readlines():  
        corpus.append(line.strip())
    vectorizer = CountVectorizer() 
    X = vectorizer.fit_transform(corpus) 
    word = vectorizer.get_feature_names()    
    for n in range(len(word)):  
        print(word[n],end=" ")
    print('')  
    print(X.toarray())
    
    #第二步 计算TF-IDF值
    transformer = TfidfTransformer()  
    print(transformer)
    tfidf = transformer.fit_transform(X)
    print(tfidf.toarray())
    weight = tfidf.toarray()
    
    #第三步 KMeans聚类
    from sklearn.cluster import KMeans  
    clf = KMeans(n_clusters=3)  
    s = clf.fit(weight) 
    y_pred = clf.fit_predict(weight)
    print(clf)
    print(clf.cluster_centers_) #类簇中心
    print(clf.inertia_)         #距离:用来评估簇的个数是否合适 越小说明簇分的越好
    print(y_pred)               #预测类标
    
    #第四步 降维处理
    from sklearn.decomposition import PCA  
    pca = PCA(n_components=2)   #降低成两维绘图 
    newData = pca.fit_transform(weight)  
    print(newData)
    x = [n[0] for n in newData]  
    y = [n[1] for n in newData]  
    
    #第五步 可视化
    import numpy as np  
    import matplotlib.pyplot as plt   
    plt.scatter(x, y, c=y_pred, s=100, marker='s')  
    plt.title("Kmeans")    
    plt.xlabel("x")  
    plt.ylabel("y")    
    plt.show() 
    

    聚类输出如图所示。

    请添加图片描述

    图中共绘制了6个点,将数据聚集为三类,对应不同的颜色。其中对应的类标为:

    • [2 0 2 0 0 0 1 1 0]

    它将第1、3行语料聚集在一起,类标为2;第2、4、5、6、9行聚集为一组,类标为0;第7、8行语料聚集为最后一组,类标为1。而真实数据集中,第1、2、3行表示贵州主题,第4、5、6行表示数据分析主题,第7、8、9行表示爱情主题,所以数据分析预测结果会存在一定误差,我们需要将误差尽可能的降低,类似于深度学习,也是在不断学习中进步。

    您可能会疑惑为什么9行数据,却只绘制了6个点呢?下面是9行数据进行降维处理生成的X和Y坐标,可以看到部分数据是一样的,这是因为这9行语料所包含的词较少,出现的频率基本都是1次,在生成词频矩阵和TF-IDF后再经降维处理可能出现相同的现象,而真实分析中语料所包含词语较多,聚类分析更多的散点更能直观地反应分析的结果。

    [[-0.19851936  0.594503  ]
     [-0.07537261  0.03666604]
     [-0.19851936  0.594503  ]
     [-0.2836149  -0.40631642]
     [-0.27797826 -0.39614944]
     [-0.25516435 -0.35198914]
     [ 0.68227073 -0.05394154]
     [ 0.68227073 -0.05394154]
     [-0.07537261  0.03666604]]
    

    研究生期间,作者在研究知识图谱、实体对齐知识时,曾采用过KMeans聚类算法对所爬取的四个主题百科数据集进行文本聚类分析,其聚类结果如图所示。

    在这里插入图片描述

    图中红色表示旅游景点主题文本、绿色表示保护动物主题文本、蓝色表示人物明星主题文本、黑色表示国家地理主题文本。从图中可以发现四类主题分别聚集成四个类簇。这是文本分析的一个简单示例,希望读者能根据本章的知识点,分析自己所研究的文本知识。


    七.总结

    前面讲述的数据分析内容几乎都是基于数字、矩阵的,而也有一部分数据分析会涉及文本处理分析,尤其是中文文本数据,它们究竟怎么处理呢?当我们通过网络爬虫得到中文语料之后,我们究竟能不能进行数据分析呢?答案肯定是能的。

    但是不同于之前的数据分析,它还需要经历中文分词、数据清洗、特征提取、向量空间模型、权重计算等步骤,将中文数据转换为数学向量的形式,这些向量就是对应的数值特征,然后才能进行相应的数据分析。本章讲解贯穿着自定义的数据集,它包含了贵州、数据分析、爱情三个主题的语料,采用KMeans聚类算法进行实例讲解,希望读者认真学习,掌握中文语料分析的方法,如何将自己的中文数据集转换成向量矩阵,再进行相关的分析。

    在这里插入图片描述

    最后希望读者能复现每一行代码,只有实践才能进步。同时更多聚类算法和原理知识,希望读者下来自行深入学习研究,也推荐大家结合Sklearn官网和开源网站学习更多的机器学习知识。

    该系列所有代码下载地址:

    感谢在求学路上的同行者,不负遇见,勿忘初心。这周的留言感慨~

    在这里插入图片描述

    (By:娜璋之家 Eastmount 2021-08-06 夜于武汉 https://blog.csdn.net/Eastmount )


    参考文献:

    • [1] 杨秀璋. 专栏:知识图谱、web数据挖掘及NLP - CSDN博客[EB/OL]. (2016-09-19)[2017-11-07]. http://blog.csdn.net/column/details/eastmount-kgdmnlp.html.
    • [2] 杨秀璋. [python] LDA处理文档主题分布及分词、词频、tfidf计算[EB/OL]. (2016-03-15)[2017-12-01]. http://blog.csdn.net/eastmount/article/details/50891162.
    • [3] 杨秀璋. [python] 使用scikit-learn工具计算文本TF-IDF值[EB/OL]. (2016-08-08)[2017-12-01]. http://blog.csdn.net/eastmount/article/details/50323063.
    • [4] 杨秀璋. [python] 基于k-means和tfidf的文本聚类代码简单实现[EB\OL]. (2016-01-16)[2017-12-01]. http://blog.csdn.net/eastmount/article/details/50473675.
    • [5] 杨秀璋. [python] Kmeans文本聚类算法+PAC降维+Matplotlib显示聚类图像[EB/OL]. (2016-01-20)[2017-12-01]. http://blog.csdn.net/eastmount/article/details/50545937.
    • [6] 张良均,王路,谭立云,苏剑林. Python数据分析与挖掘实战[M]. 北京:机械工业出版社,2016.
    • [7] (美)Wes McKinney著. 唐学韬等译. 利用Python进行数据分析[M]. 北京:机械工业出版社,2013.
    • [8] Jiawei Han,Micheline Kamber著. 范明,孟小峰译. 数据挖掘概念与技术. 北京:机械工业出版社,2007.
    展开全文
  • HTML的标签分为几类?各标签语法格式是怎样的?...内容   例如,前面文档结构中的和、和等都属于双标签。 2.单标签 单标签也被称为“空标签”,是指用一个标签符号即可完整的描述某个功能的标签,其基本语法格式如下。

    HTML的标签分为哪几类?各标签语法格式是怎样的?相信大家在学习HTML课程的时候,有讲到这方面的知识。根据标签的组成特点,通常将HTML标签分为两大类,分别是“双标签”、“单标签”,对它们的具体介绍如下。
    在这里插入图片描述

    1.双标签

    双标签也被称为“体标签”,是指由开始和结束两个标签符号组成的标签。双标签的基本语法格式如下。、
      
    <标签名>内容
      
    例如,前面文档结构中的和、和等都属于双标签。

    2.单标签

    单标签也被称为“空标签”,是指用一个标签符号即可完整的描述某个功能的标签,其基本语法格式如下。

    <标签名 />

    例如,在HTML中还有一种特殊的标签——注释标签,该标签就是一种特殊功能的单标签。如果需要在HTML文档中添加一些便于阅读和理解,但又不需要显示在页面中的注释文字,就需要使用注释标签。注释标签的基本写法。

    需要注意的是,注释内容不会显示在浏览器窗口中,但是作为HTML文档内容的一部分,注释标签可以被下载到用户的计算机上,或者用户查看源代码时也可以看到注释标签。

    以上就是为大家介绍的“HTML的标签分为哪几类?各标签语法格式是怎样的?”相关内容,希望上面的介绍能够给大家带来帮助。想要获取web前端学习路线和学习资料可以关注小千,后期分享更多web前端知识。

    本文来自千锋教育,转载请注明出处。

    展开全文
  • 常常在想,自然语言处理到底在做的是一件什么样的事情?到目前为止,我所接触到的NLP其实都是在做一件事情,即将自然语言转化为一种计算机能够理解的形式。这一点在知识图谱、信息抽取、文本摘要这...
  • 检查日志文件、读取配置 文件、处理数据元素,shell脚本可以帮助我们将文本文件中各种数据的日常处理任务自动。但仅靠shell脚本命令来处理文本文件的内容有点力不从心的。如果想在shell脚本中处理任何类型的数据,...
  • (数组中保存起始位置就好了,结束位置一定是最后) AC自动机 数组缺失 二叉树遍历 前序 中序 后序 进一步思考 二叉树序列/反序列 先序中序后序两两结合重建二叉树 先序遍历 中序遍历 后序遍历 层次遍历 输入某...
  • 服务器端进行通信,就必须选择一种文本格式进行传输,当前用到最多的可能是 JSON 格式,我们通常会将需要发送的数据序列化为 JSON 格式的字符流进行传输,本文主要介绍如何正确地在 Flutter 中使用 JSON 。 术语...
  • 格式化程序代表了内容协商的一个关键部分,是首选的绑定方式。最后,第三个 HttpRequestParameterBinding ,用于支持那些直接在方法签名中使用 HttpRequestMessage 或 HttpResponseMessage 实例的操作。 13.3.1 ...
  • 数据可视概览

    万次阅读 多人点赞 2017-12-07 12:08:07
    科学可视(Scientific Visualization)、 信息可视(Information Visualization)和可视分析学(Visual Analytics)三个学科方向通常被看成可视的三个主要分支。而将这三个分支整合在一起形成的新学科 “数据...
  • 导读介绍了一些传统但是被验证是非常有用的,现在都还在用的策略,用来对非结构文本数据提取特征。介绍在本文中,我们将研究如何处理文本数据,这无疑是最丰富的非结构数据来源...
  • 牛逼!Java 从入门到精通,超全汇总版

    万次阅读 多人点赞 2021-05-06 19:40:33
    关于 Java 变量,可以参考这篇文章 Java 中的变量有很多,很容易让初学者不知所措,这里我写了一篇 Java 变量解惑的相关文章 Java 中到底有几种变量 Java 控制流程 Java 访问控制权限 面向对象 继承 封装 多态 ...
  • 在实际项目开发中最常使用到的工具之一就是富文本编辑器,使用富文本编辑器可以实现所见即所得的效果,且所有富文本编辑器里的内容(包括图片,视频,音乐等文件)全部可以带格式的存入数据库中且只需占用一个字段。...
  • 半结构数据可以通过灵活的键值调整,获取相应信息,且数据的格式不固定,如json、XML。 3、非结构数据 不适于由二维表来表现,包含所有格式的办公文档、XML、HTML、各类报表、图片和音频、视频等。
  • 取得选中的列的文本值ddl.options[ddl.selectedIndex].innerText 取得选中值相对应的valueddl.options[ddl.selectedIndex].value获取索引var selectIndex=document.getElementById("DropDownList1").selected
  • 迁移学习在NLP中的有效性来自对具有自监督任务的丰富无标记的文本数据进行预训练的模型,例如语言建模或填写缺失的单词。 通过预先训练后,可以在较小的标记数据集上微调模型,通常比单独使用标记的数据训练更好的...
  • 大数据常用文件格式介绍

    千次阅读 2019-01-13 16:36:24
    最近在做hdfs小文件合并的项目,涉及了一些文件格式的读写,比如avro、orc、parquet等。期间阅读了一些资料,因此打算写篇文章做个记录。 这篇文章不会介绍如何对这些格式的文件进行读写,只...
  • DNS数据包格式内容详解

    万次阅读 多人点赞 2018-11-29 22:36:03
    该记录的值可以是人工可读文本,也可以是机器可读文本。 (常用夹带其他数据比如C&C和文件,用于建立隧道) 查询类型出现在问题字段中,查询类型是类型的一个超集,所有的类型都是可用的查询类型 AXFR 252 A ...
  • 数据可视平台理论与实践

    万次阅读 2017-08-02 09:32:26
    前面说完了大数据开发平台的核心组件,作业调度系统,接下来讨论一下大数据开发平台的脸面之一,数据可视平台。
  • 本文内容节选自MSDN 摘要: 1.标准 DateTime 格式字符串 2.标准数字格式字符串 3.自定义 DateTime 格式字符串 4.枚举格式字符串 5.自定义数字格式字符串   标准 DateTime 格式字符串   标准 ...
  • python3爬虫解析内容提取的几种方式 1.内容提取的方式 接上上科普篇: python3爬虫系列04之网页解析器:re正则式,BeautifulSoup库的解释,这一篇里面讲了,网页解析器的,re正则式和对BeautifulSoup库以及的他一些...
  • DDL(定义)、DML(操作)、DQL(查询)、DCL(控制) SQL语句中的快捷键 \G 格式化输出(文本式,竖立显示) \s 查看服务器端信息 \c 结束命令输入操作 \q 退出当前sql命令行模式 \h 查看帮助 操作数据库的步骤 连接, 打开库,...
  • 【论文写作技巧】Endnote参考文献统一输出格式

    万次阅读 多人点赞 2022-01-20 14:26:18
    最近在写毕业论文,参考文献之类的管理最烦了,好在之前学过一点EndNote,但并不知道怎么设置指定的参考文献格式,于是上网查看下资料进行总结。学校要求的是使用国家推荐标准GB/T 7714-2005 规范文后参考文献的...
  • 不要我讲了,这不是我在博客该讲的,言简意赅就是:通俗易懂,上手快,实用强。我会根据自己个人理解以及国外官网翻译为中文对大家进行讲解。完整源码我也会在最下面带上地址。内容过多,我不会全部演示,你完全可以...
  • 非功能测试之本地和国际测试

    万次阅读 2019-12-22 21:10:15
    说明:该篇博客是博主一字一码编写的,实属不易,...程序代码与显示内容分离三、本地测试的测试方法1.软件本地过程1.1 翻译1.2 本地工程处理1.3 桌面排版2.本地测试的测试方法2.1 多语言测试2.2 区域文化2....
  • dxf 格式详解

    万次阅读 2020-05-25 09:58:12
    DXF文件由标题段、表段、块段、实体段和文件结束段5部分组成,其内容如下。 ☆标题段(HEADER)标题段记录AutoCAD系统的所有标题变量的当前值或当前状态。标题变量记录了AutoCAD系统的当前工作环境,如SNAP捕捉...
  • 目录 第1章介绍... 1 1.1.什么是Wireshark....1.1.5.支持多格式输出... 2 1.1.6.对多种协议解码提供支持... 2 1.1.7.开源软件... 2 1.1.8.Wireshark不能做的事... 2 1.2.系通需求... 2 1.2.1.一般说明... 2 ...
  • A、OFFICE软件 B、操作系统 C、管理信息系统 D、财务管理软件 48、下列哪些不是CPU的性能指标( )。 A、主频 B、缓存 C、字长 D、品牌 49、下列设备中,属于输出设备的是( )。 A、显示器 B、键盘 C、鼠标 D、...
  • 文本标记语言 -- HTML

    万次阅读 2018-06-28 17:42:47
    ... ... ...HTML是Hyper Text Markup Language(超文本标记语言)的缩写,是构成Web页面的基本...HTML不是一种编程语言,而是一种描述性的标记语言,通过标识符来标识网页中内容的显示方式,例如图片的显示尺寸、文字的...
  • GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。 安全的方法除了 GET ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 63,712
精华内容 25,484
热门标签
关键字:

哪项不是文本格式化内容