象棋与围棋

《Fluent Python》这本书非常值得读,此书不仅讨论Python,经常结合其他编程语言讨论一些语言设计上的问题。读此书就和一位经验老道的开发者聊天一样,非常尽兴。

今天在书上(16章协程)看到这样一段话,很震撼:

对编程语言来说,关键字的作用是建立控制流程和表达式计算的基本规则。

语言的关键字像是棋盘游戏中的棋子。对国际象棋来说,关键字是王、后、车、马、象兵;对围棋来说,关键字是●。

国际象棋的棋手实现计划时,有六种类型的棋子可用;而围棋的棋手看起来只有一种类型的棋子可用。可是,在围棋的玩法中,相邻的棋子能构成更大更稳定的棋子,形状各异,不受束缚。围棋棋子的某些排列是不可摧毁的。围棋的表现力比国际象棋强。围棋的开局走法有 361 种,大约有 1e+170 个合规的位置;而国际象棋的开局走法有 20 种,有 1e+50 个位置。

如果为国际象棋添加一个新棋子,将带来颠覆性的改变;为编程语言添加一个新的关键字也是如此。因此,语言的设计者谨慎考虑引入新关键字是合理的。

Scheme 继承了 Lisp 的宏,允许任何人创建特殊的形式,为语言添加新的控制结构和计算规则。用户定义的这种标识符叫作“句法关键字”。Scheme R5RS 标准声称,“这门语言没有保留的标识符”(标准的第 45页),但是MIT/GNUScheme这种特殊的实现预定义了 34 个句法关键字,例如 if、lambda 和 definesyntax(用于创建新关键字的关键字)。

Python 像国际象棋,而 Scheme 像围棋。

现在,回到 Python 句法。我觉得 Guido 对关键字的态度过于保守了。关键字的数量应该少,添加新关键字可能会破坏大量代码,但是在循环中使用 else 揭示了一个递归问题:在更适合使用新关键字的地方重用现有的关键字。在 for、while 和 try 的上下文中,应该使用 then 关键字,而不该妄用 else。

在这个问题上,最严重的一点是重用 def。现在,这个关键字用于定义函数、生成器和协程,而这些对象之间的差异很大,不应该使用相同的句法声明。

引入 yield from 句法尤其让人失望。再次声明,我觉得真的应该为 Python 使用者提供新的关键字。更糟的是,这开启了新的趋势:把现有的关键字串起来,创建新的句法,而不添加描述性的合理关键字。恐怕有一天我们要苦苦思索 raise from lambda 是什么意思。

这段文字很有意思,作者抱怨的是,该引入新的关键字的时候却不引入,过于保守。

比如for-else语句就很让人困惑——“for循环跑完了,这段else会不会执行呢?”我每次看for-else代码,都得在心里来回算几遍,显然如果改成“then”,就一点歧义也没有了。

将人们已经熟悉的内容重复赋予新的意义,会更加让人困惑。甚至组合一些关键字来使用。

不过好在现在Python已经引入了async/await来定义协程(PEP 492),好的多了。

贴一下书中推荐的两个链接,很有意思:

  1. The Value Of Syntax? 这个讨论组叫做lambda-the-ultimate,是编程语言极客的聚集地
  2. What color is your function?
 

上海越来越冷了……

最近有很多想写的东西,这篇博客是日记,非技术。

1. 写博客

我觉得写博客和逛博客是不分家的,如果自己不写基本上没什么动力去看别人写的东西,自己写博客也会经常去看看别人的博客。关于能获得什么……现在想想看,其实并没有多少收获。不写又不会死。不玩游戏也不会死,不编程也不会死。不去做什么事情都不会死。就是一种爱好吧,想说的话能说出来,有一个属于自己的地方,不受审查和限制的地方。wordpress的后台编辑器越来越好了,打开这里觉得很幸福。

有时候在网上翻翻别人的博客,仿佛看到一个人一天一天的生活,自己也不会觉得无聊。从一个人的博客可以发现很多好玩的链接,以及友情链接,又能发现好玩的东西——这就是上网冲浪吧!

今天尝试申请Google的Adsense,一个广告服务。这是我第四次申请了,结果和前三次一样,被拒:

网站不符合Google合作规范:您的网站没有遵守Google AdSense合作规范或网站站长质量指南,因此我们目前无法批准您加入AdSense的申请。我们的目标是向广告客户提供具有以下几个特点的网站:可提供丰富、实用的内容,能够获得自然流量并且允许我们向用户投放具有理想针对性的广告。我们认为您的网站目前没有达到这一标准。

很不理解,写四年了,转载的内容不超过3篇。不过算了,不过就不过吧,一来也挣不到多少钱,二来有这东西在,写博客的时候心里总会想着写点SEO友好的、能吸引流量的东西。还是自己想写什么就写什么。

2. 厨艺

沉迷下厨无法自拔,吃自己做的意大利面就像回到了德国一样。好久没做红烧肉了,最近打算试一下,可能会尝到爱情的味道,嘻嘻。

3. 容器

最近尝试了很多虚拟化技术,非常着迷。我觉得有系统洁癖的人都会喜欢这玩意儿的。想想看,即使是在个人电脑上,所有的东西都是一个个你命名的名字有条不紊的运行着,看着多舒服。虽然我觉得个人电脑尽管折腾就行,保持服务器干净就行了……哦对了,主要容器技术最好的地方就是所有的东西都是可重置,要么有Vgrantfile,要么有Dockerfile,这些file将操作的过程全部透明化了。不用每次配置个wordpress都要在网上找教程看半天了!

Vagrant和Docker,不一样的东西。我觉得都挺好的,两手都要抓,两手都要硬。比如学习docker的swarm的时候,搞个两台机器建集群,Vagrant就再合适不过啦(虽然最后自己没搞成,偷懒用了官方的docker-machine)。

还有一个好处,有了这种东西,以后看到什么稀奇玩意儿都可以装一装尝试一下。

4. 水果

圣诞节快到了!祝读者圣诞快乐!

公司一人发了一箱苹果。乖乖,我现在有20个苹果(超级大个),9个橙子,1个牛油果,一盒红毛丹(味道和荔枝一样),可能是我这辈子拥有水果最多的时候。

5. 游戏

趁打折买了《辐射4》,沉迷捡垃圾不能自拔。就是感觉这游戏引导比较差,什么武器好使,什么建筑要先造,都要凭着之前游戏的经验。很容易迷失自我。

12月PlayStation Plus免费了《瑞奇与叮当》,下载下来一看,我去,那叫一个好玩!1080p的画质,创意丰富的游戏道具,游戏性非常好。缺点就是故事剧情太俗套,基本上没剧情。总体来讲是一个非常难得的游戏了。最近《恶灵附身》非常火,但这是个恐怖游戏,我暂时应该不会接触了…… 目前最渴望玩《使命召唤·二战》以及明年的《战神4》《最后生还者2》《蜘蛛侠》《超越善恶》,非常期待!

最近经常看“星际老男孩”的录像,发现星际2我竟然还能看得懂,哪些快捷键也都还记得住。好想再打一盘啊。但是我的键盘是60%,鼠标是人体工学的,电脑也不是windows。哎,怎么感觉跟老人一样。

6. 漫画

最近在看的书是《我们》,一个俄罗斯人写的,对乌托邦社会有理解,这位作者在俄罗斯比较出名。虽然现在的俄罗斯已经不是六七十年代恐怖的俄罗斯了。

还看了一些漫画作品,非常不错:《和女儿的日常1,2》萌萌的很可爱。《蓝色小药丸》讲了艾滋病人的故事,其实艾滋病已经没有现在的人们想象的那样可怕,艾滋病人有权力像正常人一样生活。《利维坦之书》,这个我还没看完,和《塔希里亚故事集》有点像。

7. 开源

挖了太多的坑了,每天回到家吃饱了就想当咸鱼……

希望年前(元旦是没指望了,就大年吧)把《Python并行编程》这本书翻译完(flagged)。

 

Hash碰撞和解决策略

很多语言都有Hash Table这种数据结构,有的叫哈希表,有的叫散列表,有的叫字典,有的叫map。大体上讲,这就是一种存储键值对的数据结构,通过hash算法,将给定的key算出一个hash值,将value存到hash值对应的地方。下次查找key的时候,以同样的hash算法算出key的hash值,到相应的地方找到该value。从原理可以看出,这种数据结构的好处是,无论多大的一个数据量,算法复杂度总是Ο(1)的。所以大多数的编程语言都实现了这种数据结构。

1.什么是hash碰撞?

那么从上面介绍的原理中可以看出,这种数据结构的关键就是hash算法。hash算法决定了你的key将在什么地方保存value,如果你要让各种对象都可以作为key的话,你就要为它们实现hash算法。hash算法又是一个很神奇的算法。

一个设计良好的hahs算法,具有以下特性:

  1. 压缩性:任意长度的数据都可以通过hash来压缩(或扩展)到相同长度
  2. 抗计算原性:给定一个hash结果h,寻找原来的M来满足H(M)=h是计算困难的。(hash是一种单向的函数,这个功能很强大。例如,我们在数据库保存用户的密码并不是保存密码明文,而是保存密码明文的hash值,验证密码是否正确的时候将明文hash之后与数据库保存的hash值对比即可。这样即使数据库泄露,也不会泄露用户的密码)
  3. 抗碰撞性。找到两个明文M,M’ 使hash值 H(M)=H(M’)是计算困难的

正因为有第三点,所以我们的hash表才能正常工作。但实际上,由于hash将任意长度生成固定长度的值,所以必然可能存在两个不同的值M和M’,它们的hash值相等,数据结构必须能正确处理这种情况。本文就来讨论这种情况的一些解决方案——如何处理hash碰撞(Hash Collision)。在开始讨论解决方法之前,我们先要明确下面几点:

  1. 字典(hash表等称呼在本文就统称为“字典”了)必须能够处理无限大的数据量
  2. 字典要尽量保持Ο(1)的查询速度
  3. 字典要能够处理hash一致,但是值不一致的情况,屏蔽底层细节提供保存、查询等功能
  4. hash函数的散列度越高越好(减少hash碰撞的情况)

2.字典的工作原理

在《为什么list不能作为字典的key?》一文中详细介绍了字典的工作原理。

字典其实是用一个array来保存key-value pair的,value保存的位置就是hash key的值。保存的时候计算hash(key)拿到下标,将value保存到该位置,查找的时候计算hash(key)得到下标,到该位置拿到value。这样就可以做到Ο(1)的速度。

需要注意的是,因为“hash碰撞”的存在,hash(key)得到的下标的位置存放的元素并不一样是我们要找的元素。所以一个准确的查询过程应该是:

  1. hash(key)拿到下标
  2. 拿到下标的元素(实际上Array并不只存放的value,而是一个key-value键值对),比较如果该地址的key==我要找的key,那么这个value才是我要找的value

举个例子,将  {89, 18, 49, 58, 9} 存入hash表中。

存放的步骤如下:

  1. 计算89的hash位置(为什么是hash(key, size),因为我们得到的下标值必须是一个array容量内的下标,所以跟size mask一下),得到9,位置9空,放进这个位置
  2. 计算18的位置并存放,同上
  3. 计算49的位置,发现这个位置已经有元素了:
    1. 如果这个元素==89,说明我们想要存放的元素已经放好了,结束
    2. 如果!=89,说明此位置发生hash碰撞,我们将49存入下位置9的下一个位置——位置0
  4. 继续存放下一个元素,如果没发生hash碰撞就执行步骤1,如果发生hash碰撞就执行步骤3

查询的时候,比如要查询49:

  1. 计算hash(49)的值,得到下标9,发现下标9的元素key不等于49
  2. 去位置1,发现key等于49,找到,结束

3.Probing

上面觉得这个例子,其实就是解决hash碰撞的一种方案,叫做“Probing”(其实是最简单的一种Probing,叫做Liner Probing)。如果发生了hash碰撞,就使用一种策略在Array中找到一个空的位置。查找的时候只要使用同样的策略去找,肯定能找回这个元素。Probing有几个很关键的问题需要注意:

3.1惰性删除

回想上面这个例子,现在我们要从字典中删除89这个元素,如果直接将89设置为空的话,就会产生很严重的问题:如果此时存放49,计算hash得9,位置9为空,放入,就会出现两个49在字典中;如果此时查找49,发现位置9为空,得到结果49不存在;等等。

所以要删除元素的时候,我们并不会真的将这个位置的元素删除,位置设置一个特殊的flag,表示这个地方的元素已经被删除了。并不会影响49这个元素的查询(在本例子中)。

这种方案存在的问题是:如果字典要经常删除元素,就会留下大量“尸体”,而存放新的元素需要不断扩大字典。但是如今存储空间非常廉价,所以这个方案依然是比较有效的。

3.2聚集问题

在文章一开始我就提到,字典的散列度越高越好。试想如果我们计算10个元素的hash值都一样,那么每次存放和读取都会发生Probing,读取时间就会变成最高Ο(n)复杂度。

一个简单的策略是,当字典中存放的元素超过70%,就扩大字典体积。下图描述了一个散列度尚可的字典,有一小部分的聚集,当保存的元素超过70%就扩张。

这个问题其实很有意思,有一种web攻击叫做“Hash碰撞攻击”,简单而致命。一般的web服务器都用字典这种简单有效的数据结构来保存请求参数,如果我们发送一个请求,里面所有key都是相同的hash(所以如果想要使用这种攻击方式,必须知道目标服务器/编程语言的hash策略),服务器去保存你的参数时耗费的时间从Ο(1)变成Ο(n).这里有个例子,展示了PHP保存正常的65535个int值只需要0.021s,如果保存一个恶意构造的65535个int,需要42s!

4.其他Probing方式

所以,hash函数必须减少碰撞的机会。除此之外,probing也要减少计算的机会,也就是说,probing的策略也要尽量提高散列度。

4.1 Quadratic Probing

顾名思义,就是通过一个二次方程计算下一个位置。举个例子,上面的字典我们使用如下策略,如果当前位置冲突,就使用当前位置 + 2^n来保存,即K+1,K+4,K+9,结果如下。

明显比Liner Probing的散列度高了很多。

Python使用的方程是:

如果j=3,size=32的时候,序列如下:

3 -> 11 -> 19 -> 29 -> 5 -> 6 -> 16 -> 31 -> 28 -> 13 -> 2…

这里加了个perturb,很有意思,通过这里的伪代码我发现这个值也是通过hash计算出来的。这就意味着,对Python的字典进行hash碰撞攻击是非常困难的。

4.2 Double Hashing

发生冲突的时候,使用另一个hash函数计算probing的位置。

4.3 总结

这里有一个权衡,就是存取效率(每次计算耗费的时间)和散列度的权衡(计算的次数)。

如果存取效率高了,也就是计算probing的速度快了,那么散列度就会下降。Liner Probing是一个极端,存取效率很高,只要+1就可以,但是probing的次数很多。

散列度如果高了,比如Double Hashing,使用hash函数来probing肯定散列度很高,但是每次计算都要使用hash函数,计算效率就下降。

二次方程probing是一个权衡的策略,用的比较多(Python)。

5. Closed Hashing

上面提到probing的这种方法,本质上都是“当位置冲突了想办法找到另一个位置”,存放的结构是一维数组。都叫做“Open Adressing”。

还有一种“Open hashing”,也叫做“Separate Chaining”。将相同hash值的元素用Linked List链接在一起,如果Hash碰撞,在链表中找key相等的那个元素。这种形式省去了计算地址的时间,相当于Liner Probing更极端的一种形式,存取非常块,但是聚合问题最大。实现也更加复杂,需要编程语言动态分配内存。

参考资料:

  1. How can Python dict have multiple keys with same hash? 这里有些回答非常精彩,基本解释了散列值工作的详细过程
  2. Hash碰撞与拒绝服务攻击  这篇文章讲了hash算法以及碰撞攻击
  3. Python dictionary implementation Laurent写了很多Python实现讲解,我从他博客学到了很多东西
  4. Collision Resolution CMU讲义,大多数讨论hash碰撞的地方都引用了此文,非常易读
  5. wiki open addressing
  6. hash碰撞攻击
  7. Meaning of Open hashing and Closed hashing
 

Linux的用户与管理

很久以来,我对Linux用户的概念都很模糊,只知道如果碰到“Permission Denied”就用sudo来执行,甚至在服务器上跑的很多东西都是用root来跑的。今天花时间学习了一下Linux用户有关的知识,记一下笔记。

本文所有代码都是在virtualbox虚拟机中进行,环境:Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-87-generic x86_64),用户名vagrant,密码vagrant。

一、用户与用户组

1.Linux是如何分辨每一个用户的

Linux并不是通过用户名来分辨每一个用户的,而是通过UID(计算机认识数字比较容易,而人类认识单词比较容易),我们平时所说的用户名只是便于人类操作和记忆。

对于每个文件,记录的其实是一个UID,之所以显示成用户名“vagrant”、“root”,是因为在/etc/passwd中存储了有关的用户信息,ls程序在显示的时候,会根据此文件将对应的UID显示成用户名。如果我们在/etc/passwd这个文件中将UID修改,那么ls将显示的是一个UID,而不是用户名。

用户登录Linux的流程如下:

  1. 寻找/etc/passwd是否有这个用户名,如果有,读出对象的UID和GID(group ID),以及主目录和配置
  2. 读取/etc/shadow,核对密码
  3. 进入shell

2. passwd文件和shadow文件

Linux所有的用户都保存在/etc/passwd文件中,格式如下:

每一行代表一个账户,通过:来分割信息。数一数会发现一共有7个字段,这7个字段分别代表:

  1. 账户名称
  2. 密码,可以看到所有的密码都是x,这是因为很多程序会读/etc/passwd,将用户名和密码放在一起很不安全,后来密码移动到 /etc/shadow文件中了,/etc/passwd文件只留一个x占位。
  3. UID
  4. GID
  5. 用户信息说明
  6. 主目录(即登录之后立即所在的位置)
  7. shell 登录之后启动的shell,这里有一个特殊的shell就是nologin,上面的例子中,像是backup这种账户是无法登录shell的。所以当我们新建一些系统服务的账号,类似apache,Jenkins等都可以让它们不要登录系统

/etc/shadow文件的格式如下:

可以看到,这也是用:来分割的,一共有9列,每一列代表的意思如下:

  1. 账户名称
  2. 密码
  3. 最近改动密码的日期(相对于第0天,也就是1970年1月1日的偏移天数)
  4. 密码不可改动的日期,如果是0则可以随时修改
  5. 密码需要重新更改的日期,如果不改账户将会变为过期。可以看到默认设置是99999=273年
  6. 字段5前几天进行提醒
  7. 密码过期后的宽限时间,字段5+本字段天数之后依然没有更改才算过期。宽限时间内用户登录会被强制要求修改密码
  8. 账户失效日期,到期无法使用
  9. 保留字段

3.有效用户组与初始用户组

每一个用户都有一个用户组,和UID一样,Linux分辨用户组也是通过GID的,而不是通过名字。和/etc/passwd /etc/shadow一样,对于用户组有 /etc/group /etc/gshadow

先来看/etc/group

一共四个字段:

  1. 用户组名字
  2. 用户组密码,其实很少用,和passwd一样,用x占位了
  3. GID
  4. 此用户组的用户列表,逗号分搁,没有空格(一个用户组可以有多个用户,一个用户可以在多个用户组)

/etc/gshadow的内容几乎和/etc/group一样:

要注意的事第二个字段,是密码,如果第二个字段是!,表明该用户组没有用户组管理员。不过由于sudo的存在,其实用户组管理员的功能很少使用了。

  1. 用户组名字
  2. 密码列
  3. 用户组管理员的账号
  4. 该用户组的所属账号

仔细观察你可能发现这样一个问题:在/etc/passwd里面说某一个用户的用户组ID是a,但是在/etc/group的a用户组ID下并没有看到这个账户的名字啊?

这是因为,passwd里面的用户组叫做“初始化用户组”,就是你登录到shell的用户组。这个用户组不需要写到groups里面。

如果一个用户属于很多用户组,这么这些用户组的权限,该用户都会有。那么创建一个文件,默认的用户组应该是哪个呢?

答案是“有效用户组”,通过groups命令,可以查看当前用户的所在用户组,其中,排在第一个的是有效用户组,如果创建文件就会模式使用这个用户组。

下面的命令演示了有效用户组以及用户组的切换:

需要注意的是,newgrp是通过新开一个shell来赋予用户新的用户组的权限,通过exit可以回到之前的用户组。

二、创建与管理用户

1.useradd

了解了用户和用户组的概念,接下来开始讲用户的管理。

创建一个新的账户的命令是useradd,执行这个命令需要读写/etc/passwd等文件,所以一般需要root账户来执行。

上面的代码新建了一个tom账户,可以看到,系统并没有默认为tom创建一个home目录。在CentOS中,使用useradd,系统一般会默认帮你做这几件事:

  1. /etc/passwd中创建一行与账号相关的数据
  2. /etc/shadow创建相关数据,但是密码为空
  3. /etc/group创建一个与用户名一模一样的名称
  4. /home下创建一个和用户名一样的目录,权限700

我们简单的使用useradd命令,Linux会帮我们自动配置一些参数,这些参数是参考的哪里呢?见下面两条命令的输出就明白了:

2.passwd

创建用户用useradd,然后需要为用户创建密码,使用passwd命令。需要注意的有以下几点:

  1. passwd修改自己账户的密码,普通用户需要输入旧密码,root用户不需要
  2. root用户可以修改其他用户的密码而不需要输入旧密码
  3. 普通用户在创建密码时会经过PAM模块检查密码的强度,而root无需遵守强度检查

3.其他一些修改账户的命令(sudo)

chage命令可以修改密码参数有关的信息。

usermod可以修改有效用户组、初始用户组、主目录等与用户有关的信息。

userdel可以删除用户。这个命令非常危险,将会从/etc/passwd/etc/shadow,中删除用户,也会删除/home/user用户目录。一般情况下,我们暂停某个账户可以只将账户的状态设置为不可用,例如/etc/shadow第八个字段(账号失效日期)设置为0即可。如果确实需要删除用户,也可以用userdel -r username,这样会先删除find / -user username,然后再删除用户。

4.用户可以使用的一些命令

上面介绍的都是sudo用户对账户的管理,还有一些命令普通用户就可以使用:

  1. finger查阅用户有关的信息
  2. chfn change finger,修改用户的详细信息
  3. chsh 改变默认登录的shell
  4. id可以用来查询用户的id

5.用户组的管理

在上面我们已经认识了用户和用户组,以及用户的管理。其实用户组的管理也是类似,只不过是/etc/group/etc/gshadow而已。要注意的是,newgrp是新开启一个shell,用新的环境来登录用户和用户组的。仔细想想, 这是一个很有趣的问题。正好我在写这篇文章的第三天(本文估计要写一个周),Julia写了一篇博客:How do groups work on Linux? 这篇博客正好说明了newgrp的行为:Linux运行的进程都保存着有效组GID的属性,当进程想要通过组权限(这里略去了UID检查过程)读一个文件的时候,当看一个文件是否可以读的时候,Linux检查一下此进程的有效组里面是否有等于文件的组的,如果有那么就可以读。Julia之前误认为是:Linux检查一下此进程的用户是否可以读文件,如果不可以,就看一下用户所在的组,是否有等于文件所属的组的。——这样的话,就要每次去读/etc/passwd了!

另外有关用户组还要知道的一个命令是gpasswd,这个命令可以分配组管理员。组管理员可以添加或删除用户到某一个用户组。有点像论坛的版主的意思。

三、精确的权限控制ACL

我们知道,Linux的文件系统有三组权限,分别是“owner group other”,这样无法做到对同一group的成员,或对都是others的成员控制权限。所以就有了ACL(Access Control List)。ACL可以针对单一用户、单一文件或目录进行rwx的权限设置。这是一个UNIX-Like操作系统权限外的支持项目,需要文件系统的支持(mount | grep acl)。

这里没有啥需求所以没有仔细研究,更多的资料读者可以自行搜索,推荐IBM Linux ACL 体验

四、用户切换su, su – 和sudo

su用来切换用户,使用方法是 su - username-的意思是环境变量。比如说su - vagrant那么会以vagrant来登录shell,所有的环境变量都会变成vagrant的。如果不加-那么就只是id变成vagrant,进去之后如果收MAIL,还会是原来的用户的。

su - root就可以登录root账户了!其实root可以省略,su - 就是登录root账户的意思。使用某个账户临时运行一下命令:su - root -c command,这里的root也可以省略。注意如果root账户没有密码的话,会出现 Authentication failure。

su是简单的切换命令。现在有一个问题,如果每个人都有用root账户权限的需求,那么就要有很多人知道root账户的密码,这很不安全,所以就有了sudo命令。sudo可以让你用其他用户(通常是root账户)来执行命令。使用sudo的账户必须在/etc/sudoers里面。系统验证sudo的过程如下:

  1. 当用户执行sudo命令时,系统先从/etc/sudoers文件中检查用户是否有执行权限
  2. 如果有,让用户输入自己的密码
  3. 执行sudo后面的命令

sudoers文件支持对用户组授权(以%开头)。格式如下:

其中,可以修改最后一列为NOPASSWD:ALL来免除每次运行sudo都需要的密码操作。(设置之后简直每天可以多好几秒呢!)

好了,基本的管理和操作到这里差不多了,上面的学习过程都是参考的鸟哥的Linux私房菜第三版(这本书讲的挺细的但是有点啰嗦,让人摸不透重点)。其余还提到了一些内容,下面就只列一下,有需要的可以去了解:

  1. PAM模块,对密码强度的的检查就是这个模块实现的,基本上就是个API
  2. 查询在线用户与登录日志: w, who, last, lastlog
  3. 用户之间发送消息: write, mesg, wall
  4. 发送mail:MAIL
  5. 一些用户有关的检查工具:pwck, pwconv, pwunconv, chpasswd
  6. 如何批量创建账号? passwd --stdin

本文完结!(接下来是废话了)写这篇博客花了三个星期!好吧,虽然中间有各种各样别的事情来分心。之前有人在论坛上说,《鸟哥私房菜》这本书草草看了一遍但是不想再看了,又没学到什么东西。我也有差不多类似的感受,这本书好是好,但是太详细,实践又比较少,写的比较像手册(很多书都写的像手册)。学习的过程中不要怕慢,自己多试试就好了,多记笔记。感慨一下,人家制作出来的东西,我们竟然要花这么多时间去学习,差距啊。另外Linux的东西还是要扎扎实实学,多少年过去了,这些知识都没怎么变!