2016年8月

Linux 系统中一切都是文件并有相应的文件类型

在 Unix 和它衍生的比如 Linux 系统中,一切都可以看做文件。虽然它仅仅只是一个泛泛的概念,但这是事实。如果有不是文件的,那它一定是正运行的进程。

要理解这点,可以举个例子,您的根目录(/)的空间充斥着不同类型的 Linux 文件。当您创建一个文件或向系统传输一个文件时,它会在物理磁盘上占据的一些空间,而且是一个特定的格式(文件类型)。

虽然 Linux 系统中文件和目录没有什么不同,但目录还有一个重要的功能,那就是有结构性的分组存储其它文件,以方便查找访问。所有的硬件组件都表示为文件,系统使用这些文件来与硬件通信。

这些思想是对 Linux 中的各种事物的重要阐述,因此像文档、目录(Mac OS X 和 Windows 系统下称之为文件夹)、键盘、监视器、硬盘、可移动媒体设备、打印机、调制解调器、虚拟终端,还有进程间通信(IPC)和网络通信等输入/输出资源都是定义在文件系统空间下的字节流。

一切都可看作是文件,其最显著的好处是对于上面所列出的输入/输出资源,只需要相同的一套 Linux 工具、实用程序和 API。

虽然在 Linux 中一切都可看作是文件,但也有一些特殊的文件,比如套接字和命令管道

Linux 文件类型的不同之处?

Linux 系统中有三种基本的文件类型:

  • 普通/常规文件
  • 特殊文件
  • 目录文件

普通/常规文件

它们是包含文本、数据、程序指令等数据的文件,其在 Linux 系统中是最常见的一种。包括如下:

  • 可读文件
  • 二进制文件
  • 图像文件
  • 压缩文件等等

特殊文件

特殊文件包括以下几种:

块文件 block :设备文件,对访问系统硬件部件提供了缓存接口。它们提供了一种通过文件系统与设备驱动通信的方法。

有关于块文件一个重要的性能就是它们能在指定时间内传输大块的数据和信息。

列出某目录下的块文件:

# ls -l /dev | grep "^b"

输出例子

brw-rw----  1 root disk        7,   0 May 18 10:26 loop0
brw-rw----  1 root disk        7,   1 May 18 10:26 loop1
brw-rw----  1 root disk        7,   2 May 18 10:26 loop2
brw-rw----  1 root disk        7,   3 May 18 10:26 loop3
brw-rw----  1 root disk        7,   4 May 18 10:26 loop4
brw-rw----  1 root disk        7,   5 May 18 10:26 loop5
brw-rw----  1 root disk        7,   6 May 18 10:26 loop6
brw-rw----  1 root disk        7,   7 May 18 10:26 loop7
brw-rw----  1 root disk        1,   0 May 18 10:26 ram0
brw-rw----  1 root disk        1,   1 May 18 10:26 ram1
brw-rw----  1 root disk        1,  10 May 18 10:26 ram10
brw-rw----  1 root disk        1,  11 May 18 10:26 ram11
brw-rw----  1 root disk        1,  12 May 18 10:26 ram12
brw-rw----  1 root disk        1,  13 May 18 10:26 ram13
brw-rw----  1 root disk        1,  14 May 18 10:26 ram14
brw-rw----  1 root disk        1,  15 May 18 10:26 ram15
brw-rw----  1 root disk        1,   2 May 18 10:26 ram2
brw-rw----  1 root disk        1,   3 May 18 10:26 ram3
brw-rw----  1 root disk        1,   4 May 18 10:26 ram4
brw-rw----  1 root disk        1,   5 May 18 10:26 ram5
...

字符文件 Character : 也是设备文件,对访问系统硬件组件提供了非缓冲串行接口。它们与设备的通信工作方式是一次只传输一个字符的数据。

列出某目录下的字符文件:

# ls -l /dev | grep "^c"

输出例子

crw-------  1 root root       10, 235 May 18 15:54 autofs
crw-------  1 root root       10, 234 May 18 15:54 btrfs-control
crw-------  1 root root        5,   1 May 18 10:26 console
crw-------  1 root root       10,  60 May 18 10:26 cpu_dma_latency
crw-------  1 root root       10, 203 May 18 15:54 cuse
crw-------  1 root root       10,  61 May 18 10:26 ecryptfs
crw-rw----  1 root video      29,   0 May 18 10:26 fb0
crw-rw-rw-  1 root root        1,   7 May 18 10:26 full
crw-rw-rw-  1 root root       10, 229 May 18 10:26 fuse
crw-------  1 root root      251,   0 May 18 10:27 hidraw0
crw-------  1 root root       10, 228 May 18 10:26 hpet
crw-r--r--  1 root root        1,  11 May 18 10:26 kmsg
crw-rw----+ 1 root root       10, 232 May 18 10:26 kvm
crw-------  1 root root       10, 237 May 18 10:26 loop-control
crw-------  1 root root       10, 227 May 18 10:26 mcelog
crw-------  1 root root      249,   0 May 18 10:27 media0
crw-------  1 root root      250,   0 May 18 10:26 mei0
crw-r-----  1 root kmem        1,   1 May 18 10:26 mem
crw-------  1 root root       10,  57 May 18 10:26 memory_bandwidth
crw-------  1 root root       10,  59 May 18 10:26 network_latency
crw-------  1 root root       10,  58 May 18 10:26 network_throughput
crw-rw-rw-  1 root root        1,   3 May 18 10:26 null
crw-r-----  1 root kmem        1,   4 May 18 10:26 port
crw-------  1 root root      108,   0 May 18 10:26 ppp
crw-------  1 root root       10,   1 May 18 10:26 psaux
crw-rw-rw-  1 root tty         5,   2 May 18 17:40 ptmx
crw-rw-rw-  1 root root        1,   8 May 18 10:26 random

符号链接文件 Symbolic link : 符号链接是指向系统上其他文件的引用。因此,符号链接文件是指向其它文件的文件,那些文件可以是目录或常规文件。

列出某目录下的符号链接文件:

# ls -l /dev/ | grep "^l"

输出例子

lrwxrwxrwx  1 root root             3 May 18 10:26 cdrom -> sr0
lrwxrwxrwx  1 root root            11 May 18 15:54 core -> /proc/kcore
lrwxrwxrwx  1 root root            13 May 18 15:54 fd -> /proc/self/fd
lrwxrwxrwx  1 root root             4 May 18 10:26 rtc -> rtc0
lrwxrwxrwx  1 root root             8 May 18 10:26 shm -> /run/shm
lrwxrwxrwx  1 root root            15 May 18 15:54 stderr -> /proc/self/fd/2
lrwxrwxrwx  1 root root            15 May 18 15:54 stdin -> /proc/self/fd/0
lrwxrwxrwx  1 root root            15 May 18 15:54 stdout -> /proc/self/fd/1

Linux 中使用 ln 工具就可以创建一个符号链接文件,如下所示:

# touch file1.txt
# ln -s file1.txt /home/tecmint/file1.txt  [创建符号链接文件]
# ls -l /home/tecmint/ | grep "^l"         [列出符号链接文件]

在上面的例子中,首先我们在 /tmp 目录创建了一个名叫 file1.txt 的文件,然后创建符号链接文件,将 /home/tecmint/file1.txt 指向 /tmp/file1.txt 文件。

管道 Pipe 命令管道 Named pipe : 将一个进程的输出连接到另一个进程的输入,从而允许进程间通信(IPC)的文件。

命名管道实际上是一个文件,用来使两个进程彼此通信,就像一个 Linux 管道一样。

列出某目录下的管道文件:

# ls -l | grep "^p"

输出例子:

prw-rw-r-- 1 tecmint tecmint    0 May 18 17:47 pipe1
prw-rw-r-- 1 tecmint tecmint    0 May 18 17:47 pipe2
prw-rw-r-- 1 tecmint tecmint    0 May 18 17:47 pipe3
prw-rw-r-- 1 tecmint tecmint    0 May 18 17:47 pipe4
prw-rw-r-- 1 tecmint tecmint    0 May 18 17:47 pipe5

在 Linux 中可以使用 mkfifo 工具来创建一个命名管道,如下所示:

# mkfifo pipe1
# echo "This is named pipe1" > pipe1

在上的例子中,我们创建了一个名叫 pipe1 的命名管道,然后使用 echo 命令 加入一些数据,这之后在处理输入的数据时 shell 就变成非交互式的了(LCTT 译注:被管道占住了)。

然后,我们打开另外一个 shell 终端,运行另外的命令来打印出刚加入管道的数据。

# while read line ;do echo "This was passed-'$line' "; done<pipe1

套接字文件 socket : 提供进程间通信方法的文件,它们能在运行在不同环境中的进程之间传输数据和信息。

这就是说,套接字可以为运行网络上不同机器中的进程提供数据和信息传输。

一个 socket 运行的例子就是网页浏览器连接到网站服务器的过程。

# ls -l /dev/ | grep "^s"

输出例子:

srw-rw-rw-  1 root root             0 May 18 10:26 log

下面是使用 C 语言编写的调用 socket() 系统调用的例子。

int socket_desc= socket(AF_INET, SOCK_STREAM, 0 );

上例中:

  • AF_INET 指的是地址域(IPv4)
  • SOCK_STREAM 指的是类型(默认使用 TCP 协议连接)
  • 0 指协议(IP 协议)

使用 socket_desc 来引用管道文件,它跟文件描述符是一样的,然后再使用系统函数 read()write() 来分别从这个管道文件读写数据。

目录文件

这是一些特殊的文件,既可以包含普通文件又可包含其它的特殊文件,它们在 Linux 文件系统中是以根(/)目录为起点分层组织存在的。

列出某目录下的目录文件:

# ls -l / | grep "^d" 

输出例子:

drwxr-xr-x   2 root root  4096 May  5 15:49 bin
drwxr-xr-x   4 root root  4096 May  5 15:58 boot
drwxr-xr-x   2 root root  4096 Apr 11  2015 cdrom
drwxr-xr-x  17 root root  4400 May 18 10:27 dev
drwxr-xr-x 168 root root 12288 May 18 10:28 etc
drwxr-xr-x   3 root root  4096 Apr 11  2015 home
drwxr-xr-x  25 root root  4096 May  5 15:44 lib
drwxr-xr-x   2 root root  4096 May  5 15:44 lib64
drwx------   2 root root 16384 Apr 11  2015 lost+found
drwxr-xr-x   3 root root  4096 Apr 10  2015 media
drwxr-xr-x   3 root root  4096 Feb 23 17:54 mnt
drwxr-xr-x  16 root root  4096 Apr 30 16:01 opt
dr-xr-xr-x 223 root root     0 May 18 15:54 proc
drwx------  19 root root  4096 Apr  9 11:12 root
drwxr-xr-x  27 root root   920 May 18 10:54 run
drwxr-xr-x   2 root root 12288 May  5 15:57 sbin
drwxr-xr-x   2 root root  4096 Dec  1  2014 srv
dr-xr-xr-x  13 root root     0 May 18 15:54 sys
drwxrwxrwt  13 root root  4096 May 18 17:55 tmp
drwxr-xr-x  11 root root  4096 Mar 31 16:00 usr
drwxr-xr-x  12 root root  4096 Nov 12  2015 var

您可以使用 mkdir 命令来创建一个目录。

# mkdir -m 1666 tecmint.com
# mkdir -m 1666 news.tecmint.com
# mkdir -m 1775 linuxsay.com

结论

现在应该对为什么 Linux 系统中一切都是文件以及 Linux 系统中可以存在哪些类型的文件有一个清楚的认识了。

您可以通过阅读更多有关各个文件类型的文章和对应的创建过程等来增加更多知识。我希望这篇教程对您有所帮助。有任何疑问或有补充的知识,请留下评论,一起来讨论。


via: http://www.tecmint.com/explanation-of-everything-is-a-file-and-types-of-files-in-linux/

作者:Aaron Kili 译者:runningwater 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当我们编写 shell 脚本时,我们通常会在脚本中包含其它小程序或命令,例如 awk 操作。对于 awk 而言,我们需要找一些将某些值从 shell 传递到 awk 操作中的方法。

我们可以通过在 awk 命令中使用 shell 变量达到目的,在 awk 系列的这一节中,我们将学习如何让 awk 使用 shell 变量,这些变量可能包含我们希望传递给 awk 命令的值。

有两种可能的方法可以让 awk 使用 shell 变量:

1. 使用 Shell 引用

让我们用一个示例来演示如何在一条 awk 命令中使用 shell 引用来替代一个 shell 变量。在该示例中,我们希望在文件 /etc/passwd 中搜索一个用户名,过滤并输出用户的账户信息。

因此,我们可以编写一个 test.sh 脚本,内容如下:

#!/bin/bash

### 读取用户名
read -p "请输入用户名:" username

### 在 /etc/passwd 中搜索用户名,然后在屏幕上输出详细信息
cat /etc/passwd | awk "/$username/ "' { print $0 }'

然后,保存文件并退出。

上述 test.sh 脚本中 awk 命令的说明:

cat /etc/passwd | awk "/$username/ "' { print $0 }'

"/$username/ ":该 shell 引用用于在 awk 命令中替换 shell 变量 username 的值。username 的值就是要在文件 /etc/passwd 中搜索的模式。

注意,双引号位于 awk 脚本 '{ print $0 }' 之外。

接下来给脚本添加可执行权限并运行它,操作如下:

$ chmod  +x  test.sh
$ ./text.sh 

运行脚本后,它会提示你输入一个用户名,然后你输入一个合法的用户名并回车。你将会看到来自 /etc/passwd 文件中详细的用户账户信息,如下图所示:

在 Password 文件中查找用户名的 shell 脚本

2. 使用 awk 进行变量赋值

和上面介绍的方法相比,该方法更加单,并且更好。考虑上面的示例,我们可以运行一条简单的命令来完成同样的任务。 在该方法中,我们使用 -v 选项将一个 shell 变量的值赋给一个 awk 变量。

首先,创建一个 shell 变量 username,然后给它赋予一个我们希望在 /etc/passwd 文件中搜索的名称。

username="aaronkilik"

然后输入下面的命令并回车:

# cat /etc/passwd | awk -v name="$username" ' $0 ~ name {print $0}'

使用 awk 在 Password 文件中查找用户名

上述命令的说明:

  • -v:awk 选项之一,用于声明一个变量
  • username:是 shell 变量
  • name:是 awk 变量

让我们仔细瞧瞧 awk 脚本 ' $0 ~ name {print $0}' 中的 $0 ~ name。还记得么,当我们在 awk 系列第四节中介绍 awk 比较运算符时,value ~ pattern 便是比较运算符之一,它是指:如果 value 匹配了 pattern 则返回 true

cat 命令通过管道传给 awk 的 output($0) 与模式 (aaronkilik) 匹配,该模式即为我们在 /etc/passwd 中搜索的名称,最后,比较操作返回 true。接下来会在屏幕上输出包含用户账户信息的行。

结论

我们已经介绍了 awk 功能的一个重要部分,它能帮助我们在 awk 命令中使用 shell 变量。很多时候,你都会在 shell 脚本中编写小的 awk 程序或命令,因此,你需要清晰地理解如何在 awk 命令中使用 shell 变量。

在 awk 系列的下一个部分,我们将会深入学习 awk 功能的另外一个关键部分,即流程控制语句。所以请继续保持关注,并让我们坚持学习与分享。


via: http://www.tecmint.com/use-shell-script-variable-in-awk/

作者:Aaron Kili 译者:ChrisLeeGit 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

一些新的 GNU/Linux 用户很清楚 Linux 不是 Windows,但其他人对此则不甚了解,而最好的发行版设计者们则会谨记着这两种人的存在。

Linux 的核心

不管怎么说,Nicky 看起来都不太引人注目。她已经三十岁了,却决定在离开学校多年后回到学校学习。她在海军待了六年,后来接受了一份老友给她的新工作,想试试这份工作会不会比她在军队的工作更有前途。这种换工作的事情在战后的军事后勤处非常常见。我正是因此而认识的她。她那时是一个八个州的货车运输业中介组织的区域经理,而那会我在达拉斯跑肉品包装工具的运输。

Nicky 和我在 2006 年成为了好朋友。她很外向,几乎每一个途经她负责的线路上的人她都乐于接触。我们经常星期五晚上相约去一家室内激光枪战中心打真人 CS。像这样一次就打三个的半小时战役对我们来说并不鲜见。或许这并不像彩弹游戏(LCTT 译注:一种军事游戏,双方以汽枪互射彩色染料弹丸,对方被击中后衣服上会留下彩色印渍即表示“被消灭”——必应词典)一样便宜,但是它很有临场感,还稍微有点恐怖游戏的感觉。某次活动的时候,她问我能否帮她维修电脑。

她知道我在为了让一些贫穷的孩子能拥有他们自己的电脑而奔走,当她抱怨她的电脑很慢的时候,我开玩笑地说她可以给比尔盖茨的 401k 计划交钱了(LCTT 译注:401k 计划始于20世纪80年代初,是一种由雇员、雇主共同缴费建立起来的完全基金式的养老保险制度。此处隐喻需要购买新电脑而向比尔盖茨的微软公司付费软件费用。)。Nicky 却说这是了解 Linux 的最佳时间。

她的电脑是品牌机,是个带有 Dell 19'' 显示器的 2005 年年中款的华硕电脑。不幸的是,这台电脑没有好好照料,上面充斥着所能找到的各种关都关不掉的工具栏和弹窗软件。我们把电脑上的文件都做了备份之后就开始安装 Linux 了。我们一起完成了安装,并且确信她知道了如何分区。不到一个小时,她的电脑上就有了一个“金闪闪”的 PCLinuxOS 桌面。

在她操作新系统时,她经常评论这个系统看起来多么漂亮。她并非随口一说;她为眼前光鲜亮丽的桌面着了魔。她说她的桌面漂亮的就像化了“彩妆”一样。这是我在安装系统期间特意设置的,我每次安装 Linux 的时候都会把它打扮的漂漂亮亮的。我希望这些桌面让每个人看起来都觉得漂亮。

大概第一周左右,她通过电话和邮件问了我一些常规问题,而最主要的问题还是她想知道如何保存她 OpenOffice 文件才可以让她的同事也可以打开这些文件。教一个人使用 Linux 或者 Open/LibreOffice 的时候最重要的就是教她保存文件。大多数用户在弹出的对话框中直接点了保存,结果就用默认的 开放文档格式 Open Document Format (ODF)保存了,这让他们吃了不少苦头。

曾经有过这么一件事,大约一年前或者更久,一个高中生说他没有通过期末考试,因为教授不能打开包含他的论文的文件。这引来了一些读者的激烈评论,大家都不知道这件事该怪谁,这孩子没错,而他的教授,似乎也没错。

我认识的一些大学教授他们每一个人都知道怎么打开 ODF 文件。另外,那个该死的微软在这方面做得真 XX 的不错,我觉得微软 Office 现在已经能打开 ODT 或者 ODF 文件了。不过我也不确定,毕竟我从 2005 年就没用过 Microsoft Office 了。

甚至在过去糟糕的日子里,微软公开而悍然地通过产品绑架的方式来在企业桌面领域推行他们的软件时,我和一些微软 Office 的用户在开展业务和洽谈合作时从来没有出现过问题,因为我会提前想到可能出现的问题并且不会有侥幸心理。我会发邮件给他们询问他们正在使用的 Office 版本。这样,我就可以确保以他们能够读写的格式保存文件。

说回 Nicky ,她花了很多时间学习她的 Linux 系统。我很惊奇于她的热情。

当人们意识到需要抛弃所有的 Windows 的使用习惯和工具的时候,学习 Linux 系统就会很容易。甚至在告诉那些淘气的孩子们如何使用之后,再次回来检查的时候,他们都不会试图把 .exe 文件下载到桌面上或某个下载文件夹。

在我们通常讨论这些文件的时候,我们也会提及关于更新的问题。长久以来我一直反对在一台机器上有多个软件安装系统和更新管理软件。以 Mint 来说,它完全禁用了 Synaptic 中的更新功能,这让我失去兴趣。但是即便对于我们这些仍然在使用 dpkg 和 apt 的老家伙们来说,睿智的脑袋也已经开始意识到命令行对新用户来说并不那么温馨而友好。

我曾严正抗议并强烈谴责 Synaptic 功能上的削弱,直到它说服了我。你记得什么时候第一次使用的新打造的 Linux 发行版,并拥有了最高管理权限吗? 你记得什么时候对 Synaptic 中列出的大量软件进行过梳理吗?你记得怎样开始安装每个你发现的很酷的程序吗?你记得有多少这样的程序都是以字母"lib"开头的吗?

我也曾做过那样的事。我安装又弄坏了好几次 Linux,后来我才发现那些库(lib)文件是应用程序的螺母和螺栓,而不是应用程序本身。这就是 Linux Mint 和 Ubuntu 幕后那些聪明的开发者创造了智能、漂亮和易用的应用安装器的原因。Synaptic 仍然是我们这些老玩家爱用的工具,但是对于那些在我们之后才来的新手来说,有太多的方式可以让他们安装库文件和其他类似的包。在新的安装程序中,这些文件的显示会被折叠起来,不会展示给用户。真的,这才是它应该做的。

除非你要准备好了打很多支持电话。

现在的 Linux 发行版中藏了很多智慧的结晶,我也很感谢这些开发者们,因为他们,我的工作变得更容易。不是每一个 Linux 新用户都像 Nicky 这样富有学习能力和热情。她对我来说就是一个“装好就行”的项目,只需要为她解答一些问题,其它的她会自己研究解决。像她这样极具学习能力和热情的用户的毕竟是少数。这样的 Linux 新人任何时候都是珍稀物种。

很不错,他们都是要教自己的孩子使用 Linux 的人。


via: http://fossforce.com/2016/05/anatomy-linux-user/

作者:Ken Starks 译者:vim-kakali 校对:PurlingNayuki, wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

今日关注

代号为"Aspen"的Apricity操作系统第一个稳定版本发布。该系统基于华丽的Arch Linux,可以支持GNOME 和 Cinnamon桌面环境,支持32位和64位机,搭载了最新版本的预安装软件,以及Linux4.6系列内核。这一版本的新功能中,值得一提的是采用LUKS来对整个磁盘进行加密,更稳定的安装器以及对触控板更好的支持。

图文摘要

ExTiX 16.4发布。这一系统基于Debian GNU/Linux 8.5 "Jessie", Debian GNU/Linux 9 "Stretch," 和 Ubuntu 16.04.1 LTS (Xenial Xerus) ,采用了最新的KDE4.15开发平台以及KDE 框架 5.18.0 和 Linux 内核 4.6.4。ExTiX GNU/Linux发行版被Arne Exton称之为“终极版Linux系统”,因为采用了几乎所有最新的开源应用和技术,融合了Ubuntu和Debian的核心组件。不过,该系统只支持64位机。已经可以进行下载了。

即将发布的Linux Mint 18 KDE 版本将会搭载SDDM显示管理器,不再采用MDM。另外,还会将蓝色的Linux Mint图标替换成绿色。Cinnamon 3.2桌面环境也即将发布,将会是Linux Mint 18.1默认的桌面环境,会提供对通知声音的支持,会有一个全新的屏保,在3.2版本中,还可以在屏幕上随意拖动时钟控件。敬请期待。

Python 3 的 urllib 模块是一堆可以处理 URL 的组件集合。如果你有 Python 2 的知识,那么你就会注意到 Python 2 中有 urllib 和 urllib2 两个版本的模块。这些现在都是 Python 3 的 urllib 包的一部分。当前版本的 urllib 包括下面几部分:

  • urllib.request
  • urllib.error
  • urllib.parse
  • urllib.rebotparser

接下来我们会分开讨论除了 urllib.error 以外的几部分。官方文档实际推荐你尝试第三方库, requests,一个高级的 HTTP 客户端接口。然而我依然认为知道如何不依赖第三方库打开 URL 并与之进行交互是很有用的,而且这也可以帮助你理解为什么 requests 包是如此的流行。

urllib.request

urllib.request 模块期初是用来打开和获取 URL 的。让我们看看你可以用函数 urlopen 可以做的事:

>>> import urllib.request
>>> url = urllib.request.urlopen('https://www.google.com/')
>>> url.geturl()
'https://www.google.com/'
>>> url.info()
<http.client.HTTPMessage object at 0x7fddc2de04e0>
>>> header = url.info()
>>> header.as_string()
('Date: Fri, 24 Jun 2016 18:21:19 GMT\n'
 'Expires: -1\n'
 'Cache-Control: private, max-age=0\n'
 'Content-Type: text/html; charset=ISO-8859-1\n'
 'P3P: CP="This is not a P3P policy! See '
 'https://www.google.com/support/accounts/answer/151657?hl=en for more info."\n'
 'Server: gws\n'
 'X-XSS-Protection: 1; mode=block\n'
 'X-Frame-Options: SAMEORIGIN\n'
 'Set-Cookie: '
 'NID=80=tYjmy0JY6flsSVj7DPSSZNOuqdvqKfKHDcHsPIGu3xFv41LvH_Jg6LrUsDgkPrtM2hmZ3j9V76pS4K_cBg7pdwueMQfr0DFzw33SwpGex5qzLkXUvUVPfe9g699Qz4cx9ipcbU3HKwrRYA; '
 'expires=Sat, 24-Dec-2016 18:21:19 GMT; path=/; domain=.google.com; HttpOnly\n'
 'Alternate-Protocol: 443:quic\n'
 'Alt-Svc: quic=":443"; ma=2592000; v="34,33,32,31,30,29,28,27,26,25"\n'
 'Accept-Ranges: none\n'
 'Vary: Accept-Encoding\n'
 'Connection: close\n'
 '\n')
>>> url.getcode()
200

在这里我们包含了需要的模块,然后告诉它打开 Google 的 URL。现在我们就有了一个可以交互的 HTTPResponse 对象。我们要做的第一件事是调用方法 geturl ,它会返回根据 URL 获取的资源。这可以让我们发现 URL 是否进行了重定向。

接下来调用 info ,它会返回网页的元数据,比如请求头信息。因此,我们可以将结果赋给我们的 headers 变量,然后调用它的方法 as\_string 。就可以打印出我们从 Google 收到的头信息。你也可以通过 getcode 得到网页的 HTTP 响应码,当前情况下就是 200,意思是正常工作。

如果你想看看网页的 HTML 代码,你可以调用变量 url 的方法 read。我不准备再现这个过程,因为输出结果太长了。

请注意 request 对象默认发起 GET 请求,除非你指定了它的 data 参数。如果你给它传递了 data 参数,这样 request 对象将会变成 POST 请求。


下载文件

urllib 一个典型的应用场景是下载文件。让我们看看几种可以完成这个任务的方法:

>>> import urllib.request
>>> url = 'http://www.blog.pythonlibrary.org/wp-content/uploads/2012/06/wxDbViewer.zip'
>>> response = urllib.request.urlopen(url)
>>> data = response.read()
>>> with open('/home/mike/Desktop/test.zip', 'wb') as fobj:
...     fobj.write(data)
...

这个例子中我们打开一个保存在我的博客上的 zip 压缩文件的 URL。然后我们读出数据并将数据写到磁盘。一个替代此操作的方案是使用 urlretrieve :

>>> import urllib.request
>>> url = 'http://www.blog.pythonlibrary.org/wp-content/uploads/2012/06/wxDbViewer.zip'
>>> tmp_file, header = urllib.request.urlretrieve(url)
>>> with open('/home/mike/Desktop/test.zip', 'wb') as fobj:
...     with open(tmp_file, 'rb') as tmp:
...         fobj.write(tmp.read())

方法 urlretrieve 会把网络对象拷贝到本地文件。除非你在使用 urlretrieve 的第二个参数指定你要保存文件的路径,否则这个文件将被拷贝到临时文件夹的随机命名的一个文件中。这个可以为你节省一步操作,并且使代码看起来更简单:

>>> import urllib.request
>>> url = 'http://www.blog.pythonlibrary.org/wp-content/uploads/2012/06/wxDbViewer.zip'
>>> urllib.request.urlretrieve(url, '/home/mike/Desktop/blog.zip')
('/home/mike/Desktop/blog.zip',
 <http.client.HTTPMessage object at 0x7fddc21c2470>)

如你所见,它返回了文件保存的路径,以及从请求得来的头信息。

设置你的用户代理

当你使用浏览器访问网页时,浏览器会告诉网站它是谁。这就是所谓的 user-agent (用户代理)字段。Python 的 urllib 会表示它自己为 Python-urllib/x.y , 其中 x 和 y 是你使用的 Python 的主、次版本号。有一些网站不认识这个用户代理字段,然后网站可能会有奇怪的表现或者根本不能正常工作。辛运的是你可以很轻松的设置你自己的 user-agent 字段。

>>> import urllib.request
>>> user_agent = ' Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0'
>>> url = 'http://www.whatsmyua.com/'
>>> headers = {'User-Agent': user_agent}
>>> request = urllib.request.Request(url, headers=headers)
>>> with urllib.request.urlopen(request) as response:
...     with open('/home/mdriscoll/Desktop/user_agent.html', 'wb') as out:
...         out.write(response.read())

这里设置我们的用户代理为 Mozilla FireFox ,然后我们访问 http://www.whatsmyua.com/ , 它会告诉我们它识别出的我们的 user-agent 字段。之后我们将 url 和我们的头信息传给 urlopen 创建一个 Request 实例。最后我们保存这个结果。如果你打开这个结果,你会看到我们成功的修改了自己的 user-agent 字段。使用这段代码尽情的尝试不同的值来看看它是如何改变的。


urllib.parse

urllib.parse 库是用来拆分和组合 URL 字符串的标准接口。比如,你可以使用它来转换一个相对的 URL 为绝对的 URL。让我们试试用它来转换一个包含查询的 URL :

>>> from urllib.parse import urlparse
>>> result = urlparse('https://duckduckgo.com/?q=python+stubbing&t=canonical&ia=qa')
>>> result
ParseResult(scheme='https', netloc='duckduckgo.com', path='/', params='', query='q=python+stubbing&t=canonical&ia=qa', fragment='')
>>> result.netloc
'duckduckgo.com'
>>> result.geturl()
'https://duckduckgo.com/?q=python+stubbing&t=canonical&ia=qa'
>>> result.port
None

这里我们导入了函数 urlparse , 并且把一个包含搜索查询字串的 duckduckgo 的 URL 作为参数传给它。我的查询字串是搜索关于 “python stubbing” 的文章。如你所见,它返回了一个 ParseResult 对象,你可以用这个对象了解更多关于 URL 的信息。举个例子,你可以获取到端口信息(本例中没有端口信息)、网络位置、路径和很多其它东西。

提交一个 Web 表单

这个模块还有一个方法 urlencode 可以向 URL 传输数据。 urllib.parse 的一个典型使用场景是提交 Web 表单。让我们通过搜索引擎 duckduckgo 搜索 Python 来看看这个功能是怎么工作的。

>>> import urllib.request
>>> import urllib.parse
>>> data = urllib.parse.urlencode({'q': 'Python'})
>>> data
'q=Python'
>>> url = 'http://duckduckgo.com/html/'
>>> full_url = url + '?' + data
>>> response = urllib.request.urlopen(full_url)
>>> with open('/home/mike/Desktop/results.html', 'wb') as f:
...     f.write(response.read())

这个例子很直接。基本上我们是使用 Python 而不是浏览器向 duckduckgo 提交了一个查询。要完成这个我们需要使用 urlencode 构建我们的查询字符串。然后我们把这个字符串和网址拼接成一个完整的正确 URL ,然后使用 urllib.request 提交这个表单。最后我们就获取到了结果然后保存到磁盘上。

urllib.robotparser

robotparser 模块是由一个单独的类 RobotFileParser 构成的。这个类会回答诸如一个特定的用户代理是否获取已经设置了 robot.txt 的网站的 URL。 robot.txt 文件会告诉网络爬虫或者机器人当前网站的那些部分是不允许被访问的。让我们看一个简单的例子:

>>> import urllib.robotparser
>>> robot = urllib.robotparser.RobotFileParser()
>>> robot.set_url('http://arstechnica.com/robots.txt')
None
>>> robot.read()
None
>>> robot.can_fetch('*', 'http://arstechnica.com/')
True
>>> robot.can_fetch('*', 'http://arstechnica.com/cgi-bin/')
False

这里我们导入了 robot 分析器类,然后创建一个实例。然后我们给它传递一个表明网站 robots.txt 位置的 URL 。接下来我们告诉分析器来读取这个文件。完成后,我们给它了一组不同的 URL 让它找出那些我们可以爬取而那些不能爬取。我们很快就看到我们可以访问主站但是不能访问 cgi-bin 路径。

总结一下

现在你就有能力使用 Python 的 urllib 包了。在这一节里,我们学习了如何下载文件、提交 Web 表单、修改自己的用户代理以及访问 robots.txt。 urllib 还有一大堆附加功能没有在这里提及,比如网站身份认证。你可能会考虑在使用 urllib 进行身份认证之前切换到 requests 库,因为 requests 已经以更易用和易调试的方式实现了这些功能。我同时也希望提醒你 Python 已经通过 http.cookies 模块支持 Cookies 了,虽然在 request 包里也很好的封装了这个功能。你应该可能考虑同时试试两个来决定那个最适合你。


via: http://www.blog.pythonlibrary.org/2016/06/28/python-101-an-intro-to-urllib/

作者:Mike 译者:Ezio 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

一天,有一个正在散步的妇人恰好路过一个建筑工地,看到三个正在工作的工人。她问第一个人:“你在做什么?”第一个人没好气地喊道:“你没看到我在砌砖吗?”妇人对这个答案不满意,于是问第二个人:“你在做什么?”第二个人回答说:“我在建一堵砖墙。”说完,他转向第一个人,跟他说:“嗨,你把墙砌过头了。去把刚刚那块砖弄下来!”然而,妇人对这个答案依然不满意,于是又问了第三个人相同的问题。第三个人仰头看着天,对她说:“我在建造世界上最大的教堂。”当他回答时,第一个人和第二个人在为刚刚砌错的砖而争吵。他转向那两个人,说:“不用管那块砖了。这堵墙在室内,它会被水泥填平,没人会看见它的。去砌下一层吧。”

这个故事告诉我们:如果你能够理解整个系统的构造,了解系统的各个部件如何相互结合(如砖、墙还有整个教堂),你就能够更快地定位及修复问题(那块砌错的砖)。

如果你想从头开始创造一个 Web 服务器,那么你需要做些什么呢?

我相信,如果你想成为一个更好的开发者,你必须对日常使用的软件系统的内部结构有更深的理解,包括编程语言、编译器与解释器、数据库及操作系统、Web 服务器及 Web 框架。而且,为了更好更深入地理解这些系统,你必须从头开始,用一砖一瓦来重新构建这个系统。

荀子曾经用这几句话来表达这种思想:

不闻不若闻之。 I hear and I forget.

闻之不若见之。 I see and I remember.

知之不若行之。 I do and I understand.

我希望你现在能够意识到,重新建造一个软件系统来了解它的工作方式是一个好主意。

在这个由三篇文章组成的系列中,我将会教你构建你自己的 Web 服务器。我们开始吧~

先说首要问题:Web 服务器是什么?

简而言之,它是一个运行在一个物理服务器上的网络服务器(啊呀,服务器套服务器),等待客户端向其发送请求。当它接收请求后,会生成一个响应,并回送至客户端。客户端和服务端之间通过 HTTP 协议来实现相互交流。客户端可以是你的浏览器,也可以是使用 HTTP 协议的其它任何软件。

最简单的 Web 服务器实现应该是什么样的呢?这里我给出我的实现。这个例子由 Python 写成,即使你没听说过 Python(它是一门超级容易上手的语言,快去试试看!),你也应该能够从代码及注释中理解其中的理念:

import socket

HOST, PORT = '', 8888

listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print 'Serving HTTP on port %s ...' % PORT
while True:
    client_connection, client_address = listen_socket.accept()
    request = client_connection.recv(1024)
    print request

    http_response = """\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)
    client_connection.close()

将以上代码保存为 webserver1.py,或者直接从 GitHub 上下载这个文件。然后,在命令行中运行这个程序。像这样:

$ python webserver1.py
Serving HTTP on port 8888 …

现在,在你的网页浏览器的地址栏中输入 URL:http://localhost:8888/hello ,敲一下回车,然后来见证奇迹。你应该看到“Hello, World!”显示在你的浏览器中,就像下图那样:

说真的,快去试一试。你做实验的时候,我会等着你的。

完成了?不错!现在我们来讨论一下它实际上是怎么工作的。

首先我们从你刚刚输入的 Web 地址开始。它叫 URL,这是它的基本结构:

URL 是一个 Web 服务器的地址,浏览器用这个地址来寻找并连接 Web 服务器,并将上面的内容返回给你。在你的浏览器能够发送 HTTP 请求之前,它需要与 Web 服务器建立一个 TCP 连接。然后会在 TCP 连接中发送 HTTP 请求,并等待服务器返回 HTTP 响应。当你的浏览器收到响应后,就会显示其内容,在上面的例子中,它显示了“Hello, World!”。

我们来进一步探索在发送 HTTP 请求之前,客户端与服务器建立 TCP 连接的过程。为了建立链接,它们使用了所谓“ 套接字 socket ”。我们现在不直接使用浏览器发送请求,而在命令行中使用 telnet 来人工模拟这个过程。

在你运行 Web 服务器的电脑上,在命令行中建立一个 telnet 会话,指定一个本地域名,使用端口 8888,然后按下回车:

$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.

这个时候,你已经与运行在你本地主机的服务器建立了一个 TCP 连接。在下图中,你可以看到一个服务器从头开始,到能够建立 TCP 连接的基本过程。

在同一个 telnet 会话中,输入 GET /hello HTTP/1.1,然后输入回车:

$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
GET /hello HTTP/1.1

HTTP/1.1 200 OK
Hello, World!

你刚刚手动模拟了你的浏览器(的工作)!你发送了 HTTP 请求,并且收到了一个 HTTP 应答。下面是一个 HTTP 请求的基本结构:

HTTP 请求的第一行由三部分组成:HTTP 方法(GET,因为我们想让我们的服务器返回一些内容),以及标明所需页面的路径 /hello,还有协议版本。

为了简单一些,我们刚刚构建的 Web 服务器完全忽略了上面的请求内容。你也可以试着输入一些无用内容而不是“GET /hello HTTP/1.1”,但你仍然会收到一个“Hello, World!”响应。

一旦你输入了请求行并敲了回车,客户端就会将请求发送至服务器;服务器读取请求行,就会返回相应的 HTTP 响应。

下面是服务器返回客户端(在上面的例子里是 telnet)的响应内容:

我们来解析它。这个响应由三部分组成:一个状态行 HTTP/1.1 200 OK,后面跟着一个空行,再下面是响应正文。

HTTP 响应的状态行 HTTP/1.1 200 OK 包含了 HTTP 版本号,HTTP 状态码以及 HTTP 状态短语“OK”。当浏览器收到响应后,它会将响应正文显示出来,这也就是为什么你会在浏览器中看到“Hello, World!”。

以上就是 Web 服务器的基本工作模型。总结一下:Web 服务器创建一个处于监听状态的套接字,循环接收新的连接。客户端建立 TCP 连接成功后,会向服务器发送 HTTP 请求,然后服务器会以一个 HTTP 响应做应答,客户端会将 HTTP 的响应内容显示给用户。为了建立 TCP 连接,客户端和服务端均会使用套接字。

现在,你应该了解了 Web 服务器的基本工作方式,你可以使用浏览器或其它 HTTP 客户端进行试验。如果你尝试过、观察过,你应该也能够使用 telnet,人工编写 HTTP 请求,成为一个“人形” HTTP 客户端。

现在留一个小问题:“你要如何在不对程序做任何改动的情况下,在你刚刚搭建起来的 Web 服务器上适配 Django, Flask 或 Pyramid 应用呢?”

我会在本系列的第二部分中来详细讲解。敬请期待。

顺便,我在撰写一本名为《搭个 Web 服务器:从头开始》的书。这本书讲解了如何从头开始编写一个基本的 Web 服务器,里面包含本文中没有的更多细节。订阅邮件列表,你就可以获取到这本书的最新进展,以及发布日期。


via: https://ruslanspivak.com/lsbaws-part1/

作者:Ruslan 译者:StdioA 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出