2018年2月

OpenSSH 是 SSH 协议的一个实现。一般通过 scpsftp 用于远程登录、备份、远程文件传输等功能。SSH能够完美保障两个网络或系统间数据传输的保密性和完整性。尽管如此,它最大的优势是使用公匙加密来进行服务器验证。时不时会出现关于 OpenSSH 零日漏洞的传言。本文将描述如何设置你的 Linux 或类 Unix 系统以提高 sshd 的安全性。

OpenSSH 默认设置

  • TCP 端口 - 22
  • OpenSSH 服务配置文件 - sshd_config (位于 /etc/ssh/

1、 基于公匙的登录

OpenSSH 服务支持各种验证方式。推荐使用公匙加密验证。首先,使用以下 ssh-keygen 命令在本地电脑上创建密匙对:

1024 位或低于它的 DSA 和 RSA 加密是很弱的,请不要使用。当考虑 ssh 客户端向后兼容性的时候,请使用 RSA密匙代替 ECDSA 密匙。所有的 ssh 密钥要么使用 ED25519 ,要么使用 RSA,不要使用其它类型。
$ ssh-keygen -t key_type -b bits -C "comment"

示例:

$ ssh-keygen -t ed25519 -C "Login to production cluster at xyz corp"
或
$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_aws_$(date +%Y-%m-%d) -C "AWS key for abc corp clients"

下一步,使用 ssh-copy-id 命令安装公匙:

$ ssh-copy-id -i /path/to/public-key-file user@host
或
$ ssh-copy-id user@remote-server-ip-or-dns-name

示例:

$ ssh-copy-id vivek@rhel7-aws-server

提示输入用户名和密码的时候,确认基于 ssh 公匙的登录是否工作:

$ ssh vivek@rhel7-aws-server

OpenSSH 服务安全最佳实践

更多有关 ssh 公匙的信息,参照以下文章:

2、 禁用 root 用户登录

禁用 root 用户登录前,确认普通用户可以以 root 身份登录。例如,允许用户 vivek 使用 sudo 命令以 root 身份登录。

在 Debian/Ubuntu 系统中如何将用户 vivek 添加到 sudo 组中

允许 sudo 组中的用户执行任何命令。 将用户 vivek 添加到 sudo 组中

$ sudo adduser vivek sudo

使用 id 命令 验证用户组。

$ id vivek

在 CentOS/RHEL 系统中如何将用户 vivek 添加到 sudo 组中

在 CentOS/RHEL 和 Fedora 系统中允许 wheel 组中的用户执行所有的命令。使用 usermod 命令将用户 vivek 添加到 wheel 组中:

$ sudo usermod -aG wheel vivek
$ id vivek

测试 sudo 权限并禁用 ssh root 登录

测试并确保用户 vivek 可以以 root 身份登录执行以下命令:

$ sudo -i
$ sudo /etc/init.d/sshd status
$ sudo systemctl status httpd

添加以下内容到 sshd_config 文件中来禁用 root 登录:

PermitRootLogin no
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no

更多信息参见“如何通过禁用 Linux 的 ssh 密码登录来增强系统安全” 。

3、 禁用密码登录

所有的密码登录都应该禁用,仅留下公匙登录。添加以下内容到 sshd_config 文件中:

AuthenticationMethods publickey
PubkeyAuthentication yes

CentOS 6.x/RHEL 6.x 系统中老版本的 sshd 用户可以使用以下设置:

PubkeyAuthentication yes

4、 限制用户的 ssh 访问

默认状态下,所有的系统用户都可以使用密码或公匙登录。但是有些时候需要为 FTP 或者 email 服务创建 UNIX/Linux 用户。然而,这些用户也可以使用 ssh 登录系统。他们将获得访问系统工具的完整权限,包括编译器和诸如 Perl、Python(可以打开网络端口干很多疯狂的事情)等的脚本语言。通过添加以下内容到 sshd_config 文件中来仅允许用户 root、vivek 和 jerry 通过 SSH 登录系统:

AllowUsers vivek jerry

当然,你也可以添加以下内容到 sshd_config 文件中来达到仅拒绝一部分用户通过 SSH 登录系统的效果。

DenyUsers root saroj anjali foo

你也可以通过配置 Linux PAM 来禁用或允许用户通过 sshd 登录。也可以允许或禁止一个用户组列表通过 ssh 登录系统。

5、 禁用空密码

你需要明确禁止空密码账户远程登录系统,更新 sshd_config 文件的以下内容:

PermitEmptyPasswords no

6、 为 ssh 用户或者密匙使用强密码

为密匙使用强密码和短语的重要性再怎么强调都不过分。暴力破解可以起作用就是因为用户使用了基于字典的密码。你可以强制用户避开字典密码并使用约翰的开膛手工具来检测弱密码。以下是一个随机密码生成器(放到你的 ~/.bashrc 下):

genpasswd() {
    local l=$1
    [ "$l" == "" ] && l=20
    tr -dc A-Za-z0-9_ < /dev/urandom | head -c ${l} | xargs
}

运行:

genpasswd 16

输出:

uw8CnDVMwC6vOKgW

7、 为 SSH 的 22端口配置防火墙

你需要更新 iptables/ufw/firewall-cmd 或 pf 防火墙配置来为 ssh 的 TCP 端口 22 配置防火墙。一般来说,OpenSSH 服务应该仅允许本地或者其他的远端地址访问。

Netfilter(Iptables) 配置

更新 /etc/sysconfig/iptables (Redhat 和其派生系统特有文件) 实现仅接受来自于 192.168.1.0/24 和 202.54.1.5/29 的连接,输入:

-A RH-Firewall-1-INPUT -s 192.168.1.0/24 -m state --state NEW -p tcp --dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -s 202.54.1.5/29 -m state --state NEW -p tcp --dport 22 -j ACCEPT

如果同时使用 IPv6 的话,可以编辑 /etc/sysconfig/ip6tables (Redhat 和其派生系统特有文件),输入:

-A RH-Firewall-1-INPUT -s ipv6network::/ipv6mask -m tcp -p tcp --dport 22 -j ACCEPT

ipv6network::/ipv6mask 替换为实际的 IPv6 网段。

Debian/Ubuntu Linux 下的 UFW

UFW 是 Uncomplicated FireWall 的首字母缩写,主要用来管理 Linux 防火墙,目的是提供一种用户友好的界面。输入以下命令使得系统仅允许网段 202.54.1.5/29 接入端口 22

$ sudo ufw allow from 202.54.1.5/29 to any port 22

更多信息请参见 “Linux:菜鸟管理员的 25 个 Iptables Netfilter 命令”。

*BSD PF 防火墙配置

如果使用 PF 防火墙 /etc/pf.conf 配置如下:

pass in on $ext_if inet proto tcp from {192.168.1.0/24, 202.54.1.5/29} to $ssh_server_ip port ssh flags S/SA synproxy state

8、 修改 SSH 端口和绑定 IP

ssh 默认监听系统中所有可用的网卡。修改并绑定 ssh 端口有助于避免暴力脚本的连接(许多暴力脚本只尝试端口 22)。更新文件 sshd_config 的以下内容来绑定端口 300 到 IP 192.168.1.5 和 202.54.1.5:

Port 300 
ListenAddress 192.168.1.5
ListenAddress 202.54.1.5

当需要接受动态广域网地址的连接时,使用主动脚本是个不错的选择,比如 fail2ban 或 denyhosts。

9、 使用 TCP wrappers (可选的)

TCP wrapper 是一个基于主机的访问控制系统,用来过滤来自互联网的网络访问。OpenSSH 支持 TCP wrappers。只需要更新文件 /etc/hosts.allow 中的以下内容就可以使得 SSH 只接受来自于 192.168.1.2 和 172.16.23.12 的连接:

sshd : 192.168.1.2 172.16.23.12

在 Linux/Mac OS X 和类 UNIX 系统中参见 TCP wrappers 设置和使用的常见问题

10、 阻止 SSH 破解或暴力攻击

暴力破解是一种在单一或者分布式网络中使用大量(用户名和密码的)组合来尝试连接一个加密系统的方法。可以使用以下软件来应对暴力攻击:

  • DenyHosts 是一个基于 Python SSH 安全工具。该工具通过监控授权日志中的非法登录日志并封禁原始 IP 的方式来应对暴力攻击。

    • RHEL / Fedora 和 CentOS Linux 下如何设置 DenyHosts
  • Fail2ban 是另一个类似的用来预防针对 SSH 攻击的工具。
  • sshguard 是一个使用 pf 来预防针对 SSH 和其他服务攻击的工具。
  • security/sshblock 阻止滥用 SSH 尝试登录。
  • IPQ BDB filter 可以看做是 fail2ban 的一个简化版。

11、 限制 TCP 端口 22 的传入速率(可选的)

netfilter 和 pf 都提供速率限制选项可以对端口 22 的传入速率进行简单的限制。

Iptables 示例

以下脚本将会阻止 60 秒内尝试登录 5 次以上的客户端的连入。

#!/bin/bash
inet_if=eth1
ssh_port=22
$IPT -I INPUT -p tcp --dport ${ssh_port} -i ${inet_if} -m state --state NEW -m recent --set
$IPT -I INPUT -p tcp --dport ${ssh_port} -i ${inet_if} -m state --state NEW -m recent --update --seconds 60 --hitcount 5

在你的 iptables 脚本中调用以上脚本。其他配置选项:

$IPT -A INPUT -i ${inet_if} -p tcp --dport ${ssh_port} -m state --state NEW -m limit --limit 3/min --limit-burst 3 -j ACCEPT
$IPT -A INPUT -i ${inet_if} -p tcp --dport ${ssh_port} -m state --state ESTABLISHED -j ACCEPT
$IPT -A OUTPUT -o ${inet_if} -p tcp --sport ${ssh_port} -m state --state ESTABLISHED -j ACCEPT
# another one line example
# $IPT -A INPUT -i ${inet_if} -m state --state NEW,ESTABLISHED,RELATED -p tcp --dport 22 -m limit --limit 5/minute --limit-burst 5-j ACCEPT

其他细节参见 iptables 用户手册。

*BSD PF 示例

以下脚本将限制每个客户端的连入数量为 20,并且 5 秒内的连接不超过 15 个。如果客户端触发此规则,则将其加入 abusive\_ips 表并限制该客户端连入。最后 flush 关键词杀死所有触发规则的客户端的连接。

sshd_server_ip = "202.54.1.5" 
table <abusive_ips> persist
block in quick from <abusive_ips>
pass in on $ext_if proto tcp to $sshd_server_ip port ssh flags S/SA keep state (max-src-conn 20, max-src-conn-rate 15/5, overload <abusive_ips> flush) 

12、 使用端口敲门(可选的)

端口敲门是通过在一组预先指定的封闭端口上生成连接尝试,以便从外部打开防火墙上的端口的方法。一旦指定的端口连接顺序被触发,防火墙规则就被动态修改以允许发送连接的主机连入指定的端口。以下是一个使用 iptables 实现的端口敲门的示例:

$IPT -N stage1
$IPT -A stage1 -m recent --remove --name knock
$IPT -A stage1 -p tcp --dport 3456 -m recent --set --name knock2

$IPT -N stage2
$IPT -A stage2 -m recent --remove --name knock2
$IPT -A stage2 -p tcp --dport 2345 -m recent --set --name heaven

$IPT -N door
$IPT -A door -m recent --rcheck --seconds 5 --name knock2 -j stage2
$IPT -A door -m recent --rcheck --seconds 5 --name knock -j stage1
$IPT -A door -p tcp --dport 1234 -m recent --set --name knock

$IPT -A INPUT -m --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A INPUT -p tcp --dport 22 -m recent --rcheck --seconds 5 --name heaven -j ACCEPT
$IPT -A INPUT -p tcp --syn -j door

更多信息请参见:

Debian / Ubuntu: 使用 Knockd and Iptables 设置端口敲门

13、 配置空闲超时注销时长

用户可以通过 ssh 连入服务器,可以配置一个超时时间间隔来避免无人值守的 ssh 会话。 打开 sshd_config 并确保配置以下值:

ClientAliveInterval 300
ClientAliveCountMax 0

以秒为单位设置一个空闲超时时间(300秒 = 5分钟)。一旦空闲时间超过这个值,空闲用户就会被踢出会话。更多细节参见如何自动注销空闲超时的 BASH / TCSH / SSH 用户

14、 为 ssh 用户启用警示标语

更新 sshd_config 文件如下行来设置用户的警示标语:

Banner /etc/issue

`/etc/issue 示例文件:

----------------------------------------------------------------------------------------------
You are accessing a XYZ Government (XYZG) Information System (IS) that is provided for authorized use only.
By using this IS (which includes any device attached to this IS), you consent to the following conditions:

+ The XYZG routinely intercepts and monitors communications on this IS for purposes including, but not limited to,
penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM),
law enforcement (LE), and counterintelligence (CI) investigations.

+ At any time, the XYZG may inspect and seize data stored on this IS.

+ Communications using, or data stored on, this IS are not private, are subject to routine monitoring,
interception, and search, and may be disclosed or used for any XYZG authorized purpose.

+ This IS includes security measures (e.g., authentication and access controls) to protect XYZG interests--not
for your personal benefit or privacy.

+ Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching
or monitoring of the content of privileged communications, or work product, related to personal representation
or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work
product are private and confidential. See User Agreement for details.
----------------------------------------------------------------------------------------------

以上是一个标准的示例,更多的用户协议和法律细节请咨询你的律师团队。

15、 禁用 .rhosts 文件(需核实)

禁止读取用户的 ~/.rhosts~/.shosts 文件。更新 sshd_config 文件中的以下内容:

IgnoreRhosts yes

SSH 可以模拟过时的 rsh 命令,所以应该禁用不安全的 RSH 连接。

16、 禁用基于主机的授权(需核实)

禁用基于主机的授权,更新 sshd_config 文件的以下选项:

HostbasedAuthentication no

17、 为 OpenSSH 和操作系统打补丁

推荐你使用类似 yumapt-getfreebsd-update 等工具保持系统安装了最新的安全补丁。

18、 Chroot OpenSSH (将用户锁定在主目录)

默认设置下用户可以浏览诸如 /etc/bin 等目录。可以使用 chroot 或者其他专有工具如 rssh 来保护 ssh 连接。从版本 4.8p1 或 4.9p1 起,OpenSSH 不再需要依赖诸如 rssh 或复杂的 chroot(1) 等第三方工具来将用户锁定在主目录中。可以使用新的 ChrootDirectory 指令将用户锁定在其主目录,参见这篇博文

19. 禁用客户端的 OpenSSH 服务

工作站和笔记本不需要 OpenSSH 服务。如果不需要提供 ssh 远程登录和文件传输功能的话,可以禁用 sshd 服务。CentOS / RHEL 用户可以使用 yum 命令 禁用或删除 openssh-server:

$ sudo yum erase openssh-server

Debian / Ubuntu 用户可以使用 apt 命令/apt-get 命令 删除 openssh-server:

$ sudo apt-get remove openssh-server

有可能需要更新 iptables 脚本来移除 ssh 的例外规则。CentOS / RHEL / Fedora 系统可以编辑文件 /etc/sysconfig/iptables/etc/sysconfig/ip6tables。最后重启 iptables 服务:

# service iptables restart
# service ip6tables restart

20. 来自 Mozilla 的额外提示

如果使用 6.7+ 版本的 OpenSSH,可以尝试下以下设置

#################[ WARNING ]########################
# Do not use any setting blindly. Read sshd_config #
# man page. You must understand cryptography to    #
# tweak following settings. Otherwise use defaults #
####################################################

# Supported HostKey algorithms by order of preference.
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key

# Specifies the available KEX (Key Exchange) algorithms.
KexAlgorithms [email protected],ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256

# Specifies the ciphers allowed
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr

#Specifies the available MAC (message authentication code) algorithms
MACs [email protected],[email protected],[email protected],hmac-sha2-512,hmac-sha2-256,[email protected]

# LogLevel VERBOSE logs user's key fingerprint on login. Needed to have a clear audit track of which key was using to log in.
LogLevel VERBOSE

# Log sftp level file access (read/write/etc.) that would not be easily logged otherwise.
Subsystem sftp /usr/lib/ssh/sftp-server -f AUTHPRIV -l INFO

使用以下命令获取 OpenSSH 支持的加密方法:

$ ssh -Q cipher
$ ssh -Q cipher-auth
$ ssh -Q mac
$ ssh -Q kex
$ ssh -Q key

OpenSSH安全教程查询密码和算法选择

如何测试 sshd\_config 文件并重启/重新加载 SSH 服务?

在重启 sshd 前检查配置文件的有效性和密匙的完整性,运行:

$ sudo sshd -t

扩展测试模式:

$ sudo sshd -T

最后,根据系统的的版本重启 Linux 或类 Unix 系统中的 sshd 服务

$ [sudo systemctl start ssh][38] ## Debian/Ubunt Linux##
$ [sudo systemctl restart sshd.service][39] ## CentOS/RHEL/Fedora Linux##
$ doas /etc/rc.d/sshd restart ## OpenBSD##
$ sudo service sshd restart ## FreeBSD## 

其他建议

  1. 使用 2FA 加强 SSH 的安全性 - 可以使用 OATH ToolkitDuoSecurity 启用多重身份验证。
  2. 基于密匙链的身份验证 - 密匙链是一个 bash 脚本,可以使得基于密匙的验证非常的灵活方便。相对于无密码密匙,它提供更好的安全性。

更多信息

  • OpenSSH 官方 项目。
  • 用户手册: sshd(8)、ssh(1)、ssh-add(1)、ssh-agent(1)。

如果知道这里没用提及的方便的软件或者技术,请在下面的评论中分享,以帮助读者保持 OpenSSH 的安全。

关于作者

作者是 nixCraft 的创始人,一个经验丰富的系统管理员和 Linux/Unix 脚本培训师。他曾与全球客户合作,领域涉及 IT,教育,国防和空间研究以及非营利部门等多个行业。请在 TwitterFacebookGoogle+ 上关注他。


via: https://www.cyberciti.biz/tips/linux-unix-bsd-openssh-server-best-practices.html

作者:Vivek Gite 译者:shipsw 校对:wxy

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

假设现在的上下文(LCTT 译注:context,计算机术语,此处意为业务情景)是这样的:一个 zip 文件被上传到一个Web 服务中,然后 Python 需要解压这个 zip 文件然后分析和处理其中的每个文件。这个特殊的应用查看每个文件各自的名称和大小,并和已经上传到 AWS S3 上的文件进行比较,如果文件(和 AWS S3 上的相比)有所不同或者文件本身更新,那么就将它上传到 AWS S3。

Uploads today

挑战在于这些 zip 文件太大了。它们的平均大小是 560MB 但是其中一些大于 1GB。这些文件中大多数是文本文件,但是其中同样也有一些巨大的二进制文件。不同寻常的是,每个 zip 文件包含 100 个文件但是其中 1-3 个文件却占据了多达 95% 的 zip 文件大小。

最开始我尝试在内存中解压文件,并且每次只处理一个文件。在各种内存爆炸和 EC2 耗尽内存的情况下,这个方法壮烈失败了。我觉得这个原因是这样的。最开始你有 1GB 文件在内存中,然后你现在解压每个文件,在内存中大约就要占用 2-3GB。所以,在很多次测试之后,解决方案是将这些 zip 文件复制到磁盘上(在临时目录 /tmp 中),然后遍历这些文件。这次情况好多了但是我仍然注意到了整个解压过程花费了巨量的时间。是否可能有方法优化呢?

原始函数

首先是下面这些模拟对 zip 文件中文件实际操作的普通函数:

def _count_file(fn):
 with open(fn, 'rb') as f:
   return _count_file_object(f)

def _count_file_object(f):
 # Note that this iterates on 'f'.
 # You *could* do 'return len(f.read())'
 # which would be faster but potentially memory
 # inefficient and unrealistic in terms of this
 # benchmark experiment.
 total = 0
 for line in f:
   total += len(line)
 return total

这里是可能最简单的另一个函数:

def f1(fn, dest):
 with open(fn, 'rb') as f:
   zf = zipfile.ZipFile(f)
   zf.extractall(dest)

 total = 0
 for root, dirs, files in os.walk(dest):
   for file_ in files:
     fn = os.path.join(root, file_)
     total += _count_file(fn)
 return total

如果我更仔细地分析一下,我将会发现这个函数花费时间 40% 运行 extractall,60% 的时间在遍历各个文件并读取其长度。

第一步尝试

我的第一步尝试是使用线程。先创建一个 zipfile.ZipFile 的实例,展开其中的每个文件名,然后为每一个文件开始一个线程。每个线程都给它一个函数来做“实质工作”(在这个基准测试中,就是遍历每个文件然后获取它的名称)。实际业务中的函数进行的工作是复杂的 S3、Redis 和 PostgreSQL 操作,但是在我的基准测试中我只需要制作一个可以找出文件长度的函数就好了。线程池函数:

def f2(fn, dest):

    def unzip_member(zf, member, dest):
        zf.extract(member, dest)
        fn = os.path.join(dest, member.filename)
        return _count_file(fn)

    with open(fn, 'rb') as f:
        zf = zipfile.ZipFile(f)
        futures = []
        with concurrent.futures.ThreadPoolExecutor() as executor:
            for member in zf.infolist():
                futures.append(
                    executor.submit(
                        unzip_member,
                        zf,
                        member,
                        dest,
                    )
                )
            total = 0
            for future in concurrent.futures.as_completed(futures):
                total += future.result()
    return total

结果:加速 ~10%

第二步尝试

所以可能是 GIL(LCTT 译注:Global Interpreter Lock,一种全局锁,CPython 中的一个概念)阻碍了我。最自然的想法是尝试使用多线程在多个 CPU 上分配工作。但是这样做有缺点,那就是你不能传递一个非可 pickle 序列化的对象(LCTT 译注:意为只有可 pickle 序列化的对象可以被传递),所以你只能发送文件名到之后的函数中:

def unzip_member_f3(zip_filepath, filename, dest):
    with open(zip_filepath, 'rb') as f:
        zf = zipfile.ZipFile(f)
        zf.extract(filename, dest)
    fn = os.path.join(dest, filename)
    return _count_file(fn)



def f3(fn, dest):
    with open(fn, 'rb') as f:
        zf = zipfile.ZipFile(f)
        futures = []
        with concurrent.futures.ProcessPoolExecutor() as executor:
            for member in zf.infolist():
                futures.append(
                    executor.submit(
                        unzip_member_f3,
                        fn,
                        member.filename,
                        dest,
                    )
                )
            total = 0
            for future in concurrent.futures.as_completed(futures):
                total += future.result()
    return total

结果: 加速 ~300%

这是作弊

使用处理器池的问题是这样需要存储在磁盘上的原始 .zip 文件。所以为了在我的 web 服务器上使用这个解决方案,我首先得要将内存中的 zip 文件保存到磁盘,然后调用这个函数。这样做的代价我不是很清楚但是应该不低。

好吧,再翻翻看又没有损失。可能,解压过程加速到足以弥补这样做的损失了吧。

但是一定记住!这个优化取决于使用所有可用的 CPU。如果一些其它的 CPU 需要执行在 gunicorn 中的其它事务呢?这时,这些其它进程必须等待,直到有 CPU 可用。由于在这个服务器上有其他的事务正在进行,我不是很确定我想要在进程中接管所有其他 CPU。

结论

一步一步地做这个任务的这个过程感觉挺好的。你被限制在一个 CPU 上但是表现仍然特别好。同样地,一定要看看在f1f2 两段代码之间的不同之处!利用 concurrent.futures 池类你可以获取到允许使用的 CPU 的个数,但是这样做同样给人感觉不是很好。如果你在虚拟环境中获取的个数是错的呢?或者可用的个数太低以致无法从负载分配获取好处并且现在你仅仅是为了移动负载而支付营运开支呢?

我将会继续使用 zipfile.ZipFile(file_buffer).extractall(temp_dir)。这个工作这样做已经足够好了。

想试试手吗?

我使用一个 c5.4xlarge EC2 服务器来进行我的基准测试。文件可以从此处下载:

wget https://www.peterbe.com/unzip-in-parallel/hack.unzip-in-parallel.py
wget https://www.peterbe.com/unzip-in-parallel/symbols-2017-11-27T14_15_30.zip

这里的 .zip 文件有 34MB。和在服务器上的相比已经小了很多。

hack.unzip-in-parallel.py 文件里是一团糟。它包含了大量可怕的修正和丑陋的代码,但是这只是一个开始。


via: https://www.peterbe.com/plog/fastest-way-to-unzip-a-zip-file-in-python

作者:Peterbe 译者:Leemeans 校对:wxy

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

我是一位新的 Vim 编辑器用户。我通常使用 :vs ~/.vimrc 来加载 ~/.vimrc 配置。而当编辑 .vimrc 时,我需要不重启 Vim 会话而重新加载它。在 Linux 或者类 Unix 系统中,如何在编辑 .vimrc 后,重新加载它而不用重启 Vim 呢?

Vim 是自由开源并且向上兼容 Vi 的编辑器。它可以用来编辑各种文本。它在编辑用 C/Perl/Python 编写的程序时特别有用。可以用它来编辑 Linux/Unix 配置文件。~/.vimrc 是你个人的 Vim 初始化和自定义文件。

如何在不重启 Vim 会话的情况下重新加载 .vimrc

在 Vim 中重新加载 .vimrc 而不重新启动的流程:

  1. 输入 vim filename 启动 vim
  2. 按下 Esc 接着输入 :vs ~/.vimrc 来加载 vim 配置
  3. 像这样添加自定义配置:
filetype indent plugin on
set number
syntax on
  1. 使用 :wq 保存文件,并从 ~/.vimrc 窗口退出
  2. 输入下面任一命令重载 ~/.vimrc:so $MYVIMRC 或者 :source ~/.vimrc

How to reload .vimrc file without restarting vim

图1:编辑 ~/.vimrc 并在需要时重载它而不用退出 vim,这样你就可以继续编辑程序了

:so[urce]! {file} 这个 vim 命令会从给定的文件比如 ~/.vimrc 读取配置。就像你输入的一样,这些命令是在普通模式下执行的。当你在 :global、:argdo:windo:bufdo 之后、循环中或者跟着另一个命令时,显示不会再在执行命令时更新。

如何设置按键来编辑并重载 ~/.vimrc

在你的 ~/.vimrc 后面跟上这些:

" Edit vimr configuration file
nnoremap confe :e $MYVIMRC<CR>
" Reload vims configuration file
nnoremap confr :source $MYVIMRC<CR>

现在只要按下 Esc 接着输入 confe 就可以编辑 ~/.vimrc。按下 Esc ,接着输入 confr 以重新加载。一些人喜欢在 .vimrc 中使用 <Leader> 键。因此上面的映射变成:

" Edit vimr configuration file
nnoremap <Leader>ve :e $MYVIMRC<CR>
" Reload vimr configuration file
nnoremap <Leader>vr :source $MYVIMRC<CR>

<Leader> 键默认映射成 \ 键。因此只要输入 \ 接着 ve 就能编辑文件。按下 \ 接着 vr 就能重载 ~/vimrc

这就完成了,你可以不用再重启 Vim 就能重新加载 .vimrc 了。

关于作者

作者是 nixCraft 的创建者,经验丰富的系统管理员,也是 Linux / Unix shell 脚本的培训师。他曾与全球客户以及IT、教育、国防和太空研究以及非营利部门等多个行业合作。在 Twitter、Facebook、Google + 上关注他。通过RSS/XML 订阅获取最新的系统管理、Linux/Unix 以及开源主题教程。


via: https://www.cyberciti.biz/faq/how-to-reload-vimrc-file-without-restarting-vim-on-linux-unix/

作者:Vivek Gite 译者:geekpi 校对:wxy

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

Gogs 是由 Go 语言编写的,自由开源的 Git 服务。Gogs 是一款无痛式自托管的 Git 服务器,能在尽可能小的硬件资源开销上搭建并运行您的私有 Git 服务器。Gogs 的网页界面和 GitHub 十分相近,且提供 MySQL、PostgreSQL 和 SQLite 数据库支持。

在本教程中,我们将使用 Gogs 在 Ununtu 16.04 上按步骤指导您安装和配置您的私有 Git 服务器。这篇教程中涵盖了如何在 Ubuntu 上安装 Go 语言、PostgreSQL 和安装并且配置 Nginx 网页服务器作为 Go 应用的反向代理的细节内容。

搭建环境

  • Ubuntu 16.04
  • Root 权限

我们将会接触到的事物

  1. 更新和升级系统
  2. 安装和配置 PostgreSQL
  3. 安装 Go 和 Git
  4. 安装 Gogs
  5. 配置 Gogs
  6. 运行 Gogs 服务器
  7. 安装和配置 Nginx 反向代理
  8. 测试

步骤 1 - 更新和升级系统

继续之前,更新 Ubuntu 所有的库,升级所有包。

运行下面的 apt 命令:

sudo apt update
sudo apt upgrade

步骤 2 - 安装和配置 PostgreSQL

Gogs 提供 MySQL、PostgreSQL、SQLite 和 TiDB 数据库系统支持。

此步骤中,我们将使用 PostgreSQL 作为 Gogs 程序的数据库。

使用下面的 apt 命令安装 PostgreSQL。

sudo apt install -y postgresql postgresql-client libpq-dev

安装完成之后,启动 PostgreSQL 服务并设置为开机启动。

systemctl start postgresql
systemctl enable postgresql

此时 PostgreSQL 数据库在 Ubuntu 系统上完成安装了。

之后,我们需要为 Gogs 创建数据库和用户。

使用 postgres 用户登录并运行 psql 命令以访问 PostgreSQL 操作界面。

su - postgres
psql

创建一个名为 git 的新用户,给予此用户 CREATEDB 权限。

CREATE USER git CREATEDB;
\password git

创建名为 gogs_production 的数据库,设置 git 用户作为其所有者。

CREATE DATABASE gogs_production OWNER git;

创建 Gogs 数据库

用于 Gogs 的 gogs_production PostgreSQL 数据库和 git 用户已经创建完毕。

步骤 3 - 安装 Go 和 Git

使用下面的 apt 命令从库中安装 Git。

sudo apt install git

此时,为系统创建名为 git 的新用户。

sudo adduser --disabled-login --gecos 'Gogs' git

登录 git 账户并且创建名为 local 的目录。

su - git
mkdir -p /home/git/local

切换到 local 目录,依照下方所展示的内容,使用 wget 命令下载 Go(最新版)。

cd ~/local
wget https://dl.google.com/go/go1.9.2.linux-amd64.tar.gz

安装 Go 和 Git

解压并且删除 go 的压缩文件。

tar -xf go1.9.2.linux-amd64.tar.gz
rm -f go1.9.2.linux-amd64.tar.gz

Go 二进制文件已经被下载到 ~/local/go 目录。此时我们需要设置环境变量 - 设置 GOROOTGOPATH 目录到系统环境,这样,我们就可以在 git 用户下执行 go 命令。

执行下方的命令。

cd ~/
echo 'export GOROOT=$HOME/local/go' >> $HOME/.bashrc
echo 'export GOPATH=$HOME/go' >> $HOME/.bashrc
echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' >> $HOME/.bashrc

之后通过运行 source ~/.bashrc 重载 Bash,如下:

source ~/.bashrc

确定您使用的 Bash 是默认的 shell。

安装 Go 编程语言

现在运行 go 的版本查看命令。

go version

之后确保您得到下图所示的结果。

检查 go 版本

现在,Go 已经安装在系统的 git 用户下了。

步骤 4 - 使用 Gogs 安装 Git 服务

使用 git 用户登录并且使用 go 命令从 GitHub 下载 Gogs。

su - git
go get -u github.com/gogits/gogs

此命令将在 GOPATH/src 目录下载 Gogs 的所有源代码。

切换至 $GOPATH/src/github.com/gogits/gogs 目录,并且使用下列命令搭建 Gogs。

cd $GOPATH/src/github.com/gogits/gogs
go build

确保您没有遇到错误。

现在使用下面的命令运行 Gogs Go Git 服务器。

./gogs web

此命令将会默认运行 Gogs 在 3000 端口上。

安装 Gogs Go Git 服务

打开网页浏览器,键入您的 IP 地址和端口号,我的是 http://192.168.33.10:3000/

您应该会得到与下方一致的反馈。

Gogs 网页服务器

Gogs 已经在您的 Ubuntu 系统上安装完毕。现在返回到您的终端,并且键入 Ctrl + C 中止服务。

步骤 5 - 配置 Gogs Go Git 服务器

本步骤中,我们将为 Gogs 创建惯例配置。

进入 Gogs 安装目录并新建 custom/conf 目录。

cd $GOPATH/src/github.com/gogits/gogs
mkdir -p custom/conf/

复制默认的配置文件到 custom 目录,并使用 vim 修改。

cp conf/app.ini custom/conf/app.ini
vim custom/conf/app.ini

[server] 小节中,修改 HOST_ADDR127.0.0.1

[server]
 PROTOCOL = http
 DOMAIN = localhost
 ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
 HTTP_ADDR = 127.0.0.1
 HTTP_PORT = 3000

[database] 选项中,按照您的数据库信息修改。

[database]
 DB_TYPE = postgres
 HOST = 127.0.0.1:5432
 NAME = gogs_production
 USER = git
 PASSWD = aqwe123@#

保存并退出。

运行下面的命令验证配置项。

./gogs web

并且确保您得到如下的结果。

配置服务器

Gogs 现在已经按照自定义配置下运行在 localhost 的 3000 端口上了。

步骤 6 - 运行 Gogs 服务器

这一步,我们将在 Ubuntu 系统上配置 Gogs 服务器。我们会在 /etc/systemd/system 目录下创建一个新的服务器配置文件 gogs.service

切换到 /etc/systemd/system 目录,使用 vim 创建服务器配置文件 gogs.service

cd /etc/systemd/system
vim gogs.service

粘贴下面的代码到 Gogs 服务器配置文件中。

[Unit]
Description=Gogs
After=syslog.target
After=network.target
After=mariadb.service mysqld.service postgresql.service memcached.service redis.service

[Service]
# Modify these two values and uncomment them if you have
# repos with lots of files and get an HTTP error 500 because
# of that
###
#LimitMEMLOCK=infinity
#LimitNOFILE=65535
Type=simple
User=git
Group=git
WorkingDirectory=/home/git/go/src/github.com/gogits/gogs
ExecStart=/home/git/go/src/github.com/gogits/gogs/gogs web
Restart=always
Environment=USER=git HOME=/home/git

[Install]
WantedBy=multi-user.target

之后保存并且退出。

现在可以重载系统服务器。

systemctl daemon-reload

使用下面的命令开启 Gogs 服务器并设置为开机启动。

systemctl start gogs
systemctl enable gogs

运行 Gogs 服务器

Gogs 服务器现在已经运行在 Ubuntu 系统上了。

使用下面的命令检测:

netstat -plntu
systemctl status gogs

您应该会得到下图所示的结果。

Gogs is listening on the network interface

步骤 7 - 为 Gogs 安装和配置 Nginx 反向代理

在本步中,我们将为 Gogs 安装和配置 Nginx 反向代理。我们会在自己的库中调用 Nginx 包。

使用下面的命令添加 Nginx 库。

sudo add-apt-repository -y ppa:nginx/stable

此时更新所有的库并且使用下面的命令安装 Nginx。

sudo apt update
sudo apt install nginx -y

之后,进入 /etc/nginx/sites-available 目录并且创建虚拟主机文件 gogs

cd /etc/nginx/sites-available
vim gogs

粘贴下面的代码到配置文件。

server {
     listen 80;
     server_name git.hakase-labs.co;

     location / {
         proxy_pass http://localhost:3000;
     }
 }

保存退出。

注意: 请使用您的域名修改 server_name 项。

现在激活虚拟主机并且测试 nginx 配置。

ln -s /etc/nginx/sites-available/gogs /etc/nginx/sites-enabled/
nginx -t

确保没有遇到错误,重启 Nginx 服务器。

systemctl restart nginx

安装和配置 Nginx 反向代理

步骤 8 - 测试

打开您的网页浏览器并且输入您的 Gogs URL,我的是 http://git.hakase-labs.co

现在您将进入安装界面。在页面的顶部,输入您所有的 PostgreSQL 数据库信息。

Gogs 安装

之后,滚动到底部,点击 “Admin account settings” 下拉选项。

输入您的管理者用户名和邮箱。

键入 gogs 安装设置

之后点击 “Install Gogs” 按钮。

然后您将会被重定向到下图显示的 Gogs 用户面板。

Gogs 面板

下面是 Gogs 的 “Admin Dashboard(管理员面板)”。

浏览 Gogs 面板

现在,Gogs 已经通过 PostgreSQL 数据库和 Nginx 网页服务器在您的 Ubuntu 16.04 上完成安装。


via: https://www.howtoforge.com/tutorial/how-to-install-gogs-go-git-service-on-ubuntu-1604/

作者:Muhammad Arul 译者:CYLeft 校对:wxy

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

在你的日常工作中,不可能每天都从头开始去开发一个新的应用程序。而真实的情况是,在日常工作中,我们大多数时候所面对的都是遗留下来的一个代码库,去修改一些特性的内容或者现存的一些代码行,这是我们在日常工作中很重要的一部分。而这也就是分布式版本控制系统 git 的价值所在。现在,我们来深入了解怎么去使用 git 的历史以及如何很轻松地去浏览它的历史。

Git 历史

首先和最重要的事是,什么是 git 历史?正如其名字一样,它是一个 git 仓库的提交历史。它包含一堆提交信息,其中有它们的作者的名字、该提交的哈希值以及提交日期。查看一个 git 仓库历史的方法很简单,就是一个 git log 命令。

旁注:为便于本文的演示,我们使用 Ruby on Rails 的仓库的 master 分支。之所以选择它的理由是因为,Rails 有良好的 git 历史,漂亮的提交信息、引用以及对每个变更的解释。如果考虑到代码库的大小、维护者的年龄和数量,Rails 肯定是我见过的最好的仓库。当然了,我并不是说其它的 git 仓库做的不好,它只是我见过的比较好的一个仓库。

那么,回到 Rails 仓库。如果你在 Ralis 仓库上运行 git log。你将看到如下所示的输出:

commit 66ebbc4952f6cfb37d719f63036441ef98149418
Author: Arthur Neves <[email protected]>
Date:   Fri Jun 3 17:17:38 2016 -0400

    Dont re-define class SQLite3Adapter on test

    We were declaring  in a few tests, which depending of    the order load will cause an error, as the super class could change.

    see https://github.com/rails/rails/commit/ac1c4e141b20c1067af2c2703db6e1b463b985da#commitcomment-17731383

commit 755f6bf3d3d568bc0af2c636be2f6df16c651eb1
Merge: 4e85538 f7b850e
Author: Eileen M. Uchitelle <[email protected]>
Date:   Fri Jun 3 10:21:49 2016 -0400

    Merge pull request #25263 from abhishekjain16/doc_accessor_thread

    [skip ci] Fix grammar

commit f7b850ec9f6036802339e965c8ce74494f731b4a
Author: Abhishek Jain <[email protected]>
Date:   Fri Jun 3 16:49:21 2016 +0530

    [skip ci] Fix grammar

commit 4e85538dddf47877cacc65cea6c050e349af0405
Merge: 082a515 cf2158c
Author: Vijay Dev <[email protected]>
Date:   Fri Jun 3 14:00:47 2016 +0000

    Merge branch 'master' of github.com:rails/docrails

    Conflicts:
        guides/source/action_cable_overview.md

commit 082a5158251c6578714132e5c4f71bd39f462d71
Merge: 4bd11d4 3bd30d9
Author: Yves Senn <[email protected]>
Date:   Fri Jun 3 11:30:19 2016 +0200

    Merge pull request #25243 from sukesan1984/add_i18n_validation_test

    Add i18n_validation_test

commit 4bd11d46de892676830bca51d3040f29200abbfa
Merge: 99d8d45 e98caf8
Author: Arthur Nogueira Neves <[email protected]>
Date:   Thu Jun 2 22:55:52 2016 -0400

    Merge pull request #25258 from alexcameron89/master

    [skip ci] Make header bullets consistent in engines.md

commit e98caf81fef54746126d31076c6d346c48ae8e1b
Author: Alex Kitchens <[email protected]>
Date:   Thu Jun 2 21:26:53 2016 -0500

    [skip ci] Make header bullets consistent in engines.md

正如你所见,git log 展示了提交的哈希、作者及其 email 以及该提交创建的日期。当然,git 输出的可定制性很强大,它允许你去定制 git log 命令的输出格式。比如说,我们只想看提交信息的第一行,我们可以运行 git log --oneline,它将输出一个更紧凑的日志:

66ebbc4 Dont re-define class SQLite3Adapter on test
755f6bf Merge pull request #25263 from abhishekjain16/doc_accessor_thread
f7b850e [skip ci] Fix grammar4e85538 Merge branch 'master' of github.com:rails/docrails
082a515 Merge pull request #25243 from sukesan1984/add_i18n_validation_test
4bd11d4 Merge pull request #25258 from alexcameron89/master
e98caf8 [skip ci] Make header bullets consistent in engines.md
99d8d45 Merge pull request #25254 from kamipo/fix_debug_helper_test
818397c Merge pull request #25240 from matthewd/reloadable-channels
2c5a8ba Don't blank pad day of the month when formatting dates
14ff8e7 Fix debug helper test

如果你想看 git log 的全部选项,我建议你去查阅 git log 的 man 页面,你可以在一个终端中输入 man git-log 或者 git help log 来获得。

小提示:如果你觉得 git log 看起来太恐怖或者过于复杂,或者你觉得看它太无聊了,我建议你去寻找一些 git 的 GUI 或命令行工具。在之前,我使用过 GitX ,我觉得它很不错,但是,由于我看命令行更“亲切”一些,在我尝试了 tig 之后,就再也没有去用过它。

寻找尼莫

现在,我们已经知道了关于 git log 命令的一些很基础的知识之后,我们来看一下,在我们的日常工作中如何使用它更加高效地浏览历史。

假如,我们怀疑在 String#classify 方法中有一个预期之外的行为,我们希望能够找出原因,并且定位出实现它的代码行。

为达到上述目的,你可以使用的第一个命令是 git grep,通过它可以找到这个方法定义在什么地方。简单来说,这个命令输出了匹配特定模式的那些行。现在,我们来找出定义它的方法,它非常简单 —— 我们对 def classify 运行 grep,然后看到的输出如下:

➜  git grep 'def classify'

activesupport/lib/active_support/core_ext/string/inflections.rb:    def classifyactivesupport/lib/active_support/inflector/methods.rb:    def classify(table_name)tools/profile:    def classify

现在,虽然我们已经看到这个方法是在哪里创建的,但是,并不能够确定它是哪一行。如果,我们在 git grep 命令上增加 -n 标志,git 将提供匹配的行号:

➜  git grep -n 'def classify'

activesupport/lib/active_support/core_ext/string/inflections.rb:205:  def classifyactivesupport/lib/active_support/inflector/methods.rb:186:    def classify(table_name)tools/profile:112:    def classify

更好看了,是吧?考虑到上下文,我们可以很轻松地找到,这个方法在 activesupport/lib/active_support/core_ext/string/inflections.rb 的第 205 行的 classify 方法,它看起来像这样,是不是很容易?

# Creates a class name from a plural table name like Rails does for table names to models.
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
#   'ham_and_eggs'.classify # => "HamAndEgg"
#   'posts'.classify        # => "Post"
    def classify
        ActiveSupport::Inflector.classify(self)
    end

尽管我们找到的这个方法是在 String 上的一个常见的调用,它调用了 ActiveSupport::Inflector 上的另一个同名的方法。根据之前的 git grep 的结果,我们可以很轻松地发现结果的第二行, activesupport/lib/active_support/inflector/methods.rb 在 186 行上。我们正在寻找的方法是这样的:

# Creates a class name from a plural table name like Rails does for table
# names to models. Note that this returns a string and not a Class (To
# convert to an actual class follow +classify+ with constantize).
#
#   classify('ham_and_eggs') # => "HamAndEgg"
#   classify('posts')        # => "Post"
#
# Singular names are not handled correctly:
#
#   classify('calculus')     # => "Calculus"
def classify(table_name)
    # strip out any leading schema name
    camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end

酷!考虑到 Rails 仓库的大小,我们借助 git grep 找到它,用时都没有超越 30 秒。

那么,最后的变更是什么?

现在,我们已经找到了所要找的方法,现在,我们需要搞清楚这个文件所经历的变更。由于我们已经知道了正确的文件名和行数,我们可以使用 git blame。这个命令展示了一个文件中每一行的最后修订者和修订的内容。我们来看一下这个文件最后的修订都做了什么:

git blame activesupport/lib/active_support/inflector/methods.rb

虽然我们得到了这个文件每一行的最后的变更,但是,我们更感兴趣的是对特定方法(176 到 189 行)的最后变更。让我们在 git blame 命令上增加一个选项,让它只显示那些行的变化。此外,我们将在命令上增加一个 -s (忽略)选项,去跳过那一行变更时的作者名字和修订(提交)的时间戳:

git blame -L 176,189 -s activesupport/lib/active_support/inflector/methods.rb

9fe8e19a 176)   #Creates a class name from a plural table name like Rails does for table
5ea3f284 177)   # names to models. Note that this returns a string and not a Class (To
9fe8e19a 178)   # convert to an actual class follow +classify+ with #constantize).
51cd6bb8 179)   #
6d077205 180)   #   classify('ham_and_eggs') # => "HamAndEgg"
9fe8e19a 181)   #   classify('posts')        # => "Post"
51cd6bb8 182)   #
51cd6bb8 183)   # Singular names are not handled correctly:
5ea3f284 184)   #
66d6e7be 185)   #   classify('calculus')     # => "Calculus"
51cd6bb8 186)   def classify(table_name)
51cd6bb8 187)     # strip out any leading schema name
5bb1d4d2 188)     camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
51cd6bb8 189)     end

现在,git blame 命令的输出展示了指定行的全部内容以及它们各自的修订。让我们来看一下指定的修订,换句话说就是,每个变更都修订了什么,我们可以使用 git show 命令。当指定一个修订哈希(像 66d6e7be)作为一个参数时,它将展示这个修订的全部内容。包括作者名字、时间戳以及完整的修订内容。我们来看一下 188 行最后的修订都做了什么?

git show 5bb1d4d2

你亲自做实验了吗?如果没有做,我直接告诉你结果,这个令人惊叹的 提交 是由 Schneems 完成的,他通过使用 frozen 字符串做了一个非常有趣的性能优化,这在我们当前的场景中是非常有意义的。但是,由于我们在这个假设的调试会话中,这样做并不能告诉我们当前问题所在。因此,我们怎么样才能够通过研究来发现,我们选定的方法经过了哪些变更?

搜索日志

现在,我们回到 git 日志,现在的问题是,怎么能够看到 classify 方法经历了哪些修订?

git log 命令非常强大,因此它提供了非常多的列表选项。我们尝试使用 -p 选项去看一下保存了这个文件的 git 日志内容,这个选项的意思是在 git 日志中显示这个文件的完整补丁:

git log -p activesupport/lib/active_support/inflector/methods.rb

这将给我们展示一个很长的修订列表,显示了对这个文件的每个修订。但是,正如下面所显示的,我们感兴趣的是对指定行的修订。对命令做一个小的修改,只显示我们希望的内容:

git log -L 176,189:activesupport/lib/active_support/inflector/methods.rb

git log 命令接受 -L 选项,它用一个行的范围和文件名做为参数。它的格式可能有点奇怪,格式解释如下:

git log -L <start-line>,<end-line>:<path-to-file>

当我们运行这个命令之后,我们可以看到对这些行的一个修订列表,它将带我们找到创建这个方法的第一个修订:

commit 51xd6bb829c418c5fbf75de1dfbb177233b1b154
Author: Foo Bar <[email protected]>
Date:   Tue Jun 7 19:05:09 2011 -0700

    Refactor

diff--git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -58,0 +135,14 @@
+    # Create a class name from a plural table name like Rails does for table names to models.
+    # Note that this returns a string and not a Class. (To convert to an actual class
+    # follow +classify+ with +constantize+.)
+    #
+    # Examples:
+    #   "egg_and_hams".classify # => "EggAndHam"
+    #   "posts".classify        # => "Post"
+    #
+    # Singular names are not handled correctly:
+    #   "business".classify     # => "Busines"
+    def classify(table_name)
+      # strip out any leading schema name
+      camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+    end

现在,我们再来看一下 —— 它是在 2011 年提交的。git 可以让我们重回到这个时间。这是一个很好的例子,它充分说明了足够的提交信息对于重新了解当时的上下文环境是多么的重要,因为从这个提交信息中,我们并不能获得足够的信息来重新理解当时的创建这个方法的上下文环境,但是,话说回来,你不应该对此感到恼怒,因为,你看到的这些项目,它们的作者都是无偿提供他们的工作时间和精力来做开源工作的。(向开源项目贡献者致敬!)

回到我们的正题,我们并不能确认 classify 方法最初实现是怎么回事,考虑到这个第一次的提交只是一个重构。现在,如果你认为,“或许、有可能、这个方法不在 176 行到 189 行的范围之内,那么就你应该在这个文件中扩大搜索范围”,这样想是对的。我们看到在它的修订提交的信息中提到了“重构”这个词,它意味着这个方法可能在那个文件中是真实存在的,而且是在重构之后它才存在于那个行的范围内。

但是,我们如何去确认这一点呢?不管你信不信,git 可以再次帮助你。git log 命令有一个 -S 选项,它可以传递一个特定的字符串作为参数,然后去查找代码变更(添加或者删除)。也就是说,如果我们执行 git log -S classify 这样的命令,我们可以看到所有包含 classify 字符串的变更行的提交。

如果你在 Ralis 仓库上运行上述命令,首先你会发现这个命令运行有点慢。但是,你应该会发现 git 实际上解析了在那个仓库中的所有修订来匹配这个字符串,其实它的运行速度是非常快的。在你的指尖下 git 再次展示了它的强大之处。因此,如果去找关于 classify 方法的第一个修订,我们可以运行如下的命令:

git log -S 'def classify'

它将返回所有这个方法的引用和修改的地方。如果你一直往下看,你将看到日志中它的最后的提交:

commit db045dbbf60b53dbe013ef25554fd013baf88134
Author: David Heinemeier Hansson <[email protected]>
Date:   Wed Nov 24 01:04:44 2004 +0000
    Initial
    git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4 5ecf4fe2-1ee6-0310-87b1-e25e094e27de

很酷!是吧?它初次被提交到 Rails,是由 DHH 在一个 svn 仓库上做的!这意味着 classify 大概在一开始就被提交到了 Rails 仓库。现在,我们去看一下这个提交的所有变更信息,我们运行如下的命令:

git show db045dbbf60b53dbe013ef25554fd013baf88134

非常好!我们终于找到它的根源了。现在,我们使用 git log -S 'def classify' 的输出,结合 git log -L 命令来跟踪这个方法都发生了哪些变更。

下次见

当然,我们并没有真的去修改任何 bug,因为我们只是去尝试使用一些 git 命令,来演示如何查看 classify 方法的演变历史。但是不管怎样,git 是一个非常强大的工具,我们必须学好它、用好它。我希望这篇文章可以帮助你掌握更多的关于如何使用 git 的知识。

你喜欢这些内容吗?


作者简介:

后端工程师,对 Ruby、Go、微服务、构建弹性架构来解决大规模部署带来的挑战很感兴趣。我在阿姆斯特丹的 Rails Girls 担任顾问,维护着一个小而精的列表,并且经常为开源做贡献。

那个列表是我写的关于软件开发、编程语言以及任何我感兴趣的东西。


via: https://ieftimov.com/learn-your-tools-navigating-git-history

作者:Ilija Eftimov 译者:qhwdw 校对:wxy

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

我们的手机、主机以及笔记本电脑这样的数字设备已经变得如此成熟,以至于它们进化成为我们的一部分,而不只是一种设备。

在应用和软件的帮助下,处理器执行许多任务。我们是否曾经想过是什么给了这些软件这样的能力?它们是如何执行它们的逻辑的?它们的大脑在哪?

我们知道 CPU (或称处理器)是那些需要处理数据和执行逻辑任务的设备的大脑。

cpu image

在处理器的深处有那些不一样的概念呢?它们是如何演化的?一些处理器是如何做到比其它处理器更快的?让我们来看看关于处理器的主要术语,以及它们是如何影响处速度的。

架构

处理器有不同的架构,你一定遇到过不同类型的程序说它们是 64 位或 32 位的,这其中的意思就是程序支持特定的处理器架构。

如果一颗处理器是 32 位的架构,这意味着这颗处理器能够在一个处理周期内处理一个 32 位的数据。

同理可得,64 位的处理器能够在一个周期内处理一个 64 位的数据。

同时,你可以使用的内存大小决定于处理器的架构,你可以使用的内存总量为 2 的处理器架构的幂次方(如:2^64)。

16 位架构的处理器,仅仅有 64 kb 的内存使用。32 位架构的处理器,最大可使用的 RAM 是 4 GB,64 位架构的处理器的可用内存是 16 EB。

核心

在电脑上,核心是基本的处理单元。核心接收指令并且执行它。越多的核心带来越快的速度。把核心比作工厂里的工人,越多的工人使工作能够越快的完成。另一方面,工人越多,你所付出的薪水也就越多,工厂也会越拥挤;相对于核心来说,越多的核心消耗更多的能量,比核心少的 CPU 更容易发热。

时钟速度

CPU CLOCK SPEED

GHz 是 GigaHertz 的简写,Giga 意思是 10 亿次,Hertz (赫兹)意思是一秒有几个周期,2 GHz 的处理器意味着处理器一秒能够执行 20 亿个周期 。

它也以“频率”或者“时钟速度”而熟知。这项数值越高,CPU 的性能越好。

CPU 缓存

CPU 缓存是处理器内部的一块小的存储单元,用来存储一些内存。不管如何,我们需要执行一些任务时,数据需要从内存传递到 CPU,CPU 的工作速度远快于内存,CPU 在大多数时间是在等待从内存传递过来的数据,而此时 CPU 是处于空闲状态的。为了解决这个问题,内存持续的向 CPU 缓存发送数据。

一般的处理器会有 2 ~ 3 Mb 的 CPU 缓存。高端的处理器会有 6 Mb 的 CPU 缓存,越大的缓存,意味着处理器更好。

印刷工艺

晶体管的大小就是处理器平板印刷的大小,尺寸通常是纳米,更小的尺寸意味者更紧凑。这可以让你有更多的核心,更小的面积,更小的能量消耗。

最新的 Intel 处理器有 14 nm 的印刷工艺。

热功耗设计(TDP)

代表着平均功耗,单位是瓦特,是在全核心激活以基础频率来处理 Intel 定义的高复杂度的负载时,处理器所散失的功耗。

所以,越低的热功耗设计对你越好。一个低的热功耗设计不仅可以更好的利用能量,而且产生更少的热量。

battery

桌面版的处理器通常消耗更多的能量,热功耗消耗的能量能在 40% 以上,相对应的移动版本只有不到桌面版本的 1/3。

内存支持

我们已经提到了处理器的架构是如何影响到我们能够使用的内存总量,但这只是理论上而已。在实际的应用中,我们所能够使用的内存的总量对于处理器的规格来说是足够的,它通常是由处理器规格详细规定的。

RAM

它也指出了内存所支持的 DDR 的版本号。

超频

前面我们讲过时钟频率,超频是程序强迫 CPU 执行更多的周期。游戏玩家经常会使他们的处理器超频,以此来获得更好的性能。这样确实会增加速度,但也会增加消耗的能量,产生更多的热量。

一些高端的处理器允许超频,如果我们想让一个不支持超频的处理器超频,我们需要在主板上安装一个新的 BIOS 。 这样通常会成功,但这种情况是不安全的,也是不建议的。

超线程(HT)

如果不能添加核心以满足特定的处理需要,那么超线程是建立一个虚拟核心的方式。

如果一个双核处理器有超线程,那么这个双核处理器就有两个物理核心和两个虚拟核心,在技术上讲,一个双核处理器拥有四个核心。

结论

处理器有许多相关的数据,这些对数字设备来说是最重要的部分。我们在选择设备时,我们应该在脑海中仔细的检查处理器在上面提到的数据。

时钟速度、核心数、CPU 缓存,以及架构是最重要的数据。印刷尺寸以及热功耗设计重要性差一些 。

仍然有疑惑? 欢迎评论,我会尽快回复的。


via: http://www.theitstuff.com/processors-everything-need-know

作者:Rishabh Kandari 译者:singledo 校对:wxy

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