Linux/Unix 中的标准输出与错误输出在 Linux/Unix 系统中，屏幕上显示的信息通常来源于 stdout（Standard Output，标准输出）或者 stderr（Standard Error，标准错误输出）。许多人会问：output 就是 output，直接送到屏幕上不就行了，为什么要分成 stdout 和 stderr 呢？这是因为在服务器工作环境下，几乎所有的程序都是运行在后台（background）的。为了方便调试（debug），一般在设计程序时，会将正常的 stdout 送到一个文件，而将错误的信息 stderr 存到不同的文件中。正常的 output：例如程序开始运行的时间、当前在线人数等。错误的 output：例如无法找到用户想要访问的 URL，或者信用卡认证失败等。什么是 /dev/null有了上述认知后，我们再来看什么是 > /dev/null。这条命令的意思是将 stdout 送到 /dev/null 里面。那么，什么是 /dev/null 呢？/dev/null 是 Unix/Linux 里的「无底洞」。任何 output 送去了「无底洞」就再也无法找回了。相信我，数据真的消失了！那么，在什么情况下要把 output 送去这个无底洞呢？这里没有标准答案，但通常来说，如果你不想看到 output，或者 output 太多太大，有可能把硬盘占满的时候，程序设计就会考虑将 output 送到 /dev/null。重定向语法详解：>/dev/null 2>&1在 Shell 脚本中，经常能看到 >/dev/null 2>&1 这样的写法。这条命令的意思是将标准输出和错误输出全部重定向到 /dev/null 中，也就是将产生的所有信息丢弃。命令的结果可以通过 %> 的形式来定义输出。我们可以将这个组合 >/dev/null 2>&1 分解为五个部分来理解：>：代表重定向到哪里。例如：echo "123" > /home/123.txt。/dev/null：代表空设备文件。2>：表示 stderr（标准错误）。&：表示等同于的意思。2>&1 表示 2 的输出重定向等同于 1。1：表示 stdout（标准输出）。系统默认值是 1，所以 >/dev/null 等同于 1>/dev/null。因此，>/dev/null 2>&1 也可以写成 1>/dev/null 2>&1。执行过程解析上述语句的执行过程如下：1>/dev/null：首先表示标准输出重定向到空设备文件，也就是不输出任何信息到终端，说白了就是不显示任何信息。2>&1：接着，标准错误输出重定向到标准输出。因为之前标准输出已经重定向到了空设备文件，所以标准错误输出也随之重定向到空设备文件。command > file 2>file 与 command > file 2>&1 的区别下面就为大家说明一下，command > file 2>file 与 command > file 2>&1 有什么不同的地方。command > file 2>file意思是将命令所产生的标准输出信息，和错误的输出信息送到 file 中。这样的写法，stdout 和 stderr 都直接送到 file 中，file 会被打开两次。这样 stdout 和 stderr 会互相覆盖，相当使用了 FD1 和 FD2 两个文件描述符同时去抢占 file 的管道。command > file 2>&1这条命令将 stdout 直接送向 file，stderr 继承了 FD1 管道后，再被送往 file。此时，file 只被打开了一次，也只使用了一个管道 FD1，它包括了 stdout 和 stderr 的内容。从 I/O 效率上来看，前一条命令的效率要比后一条命令的效率低。所以在编写 Shell 脚本的时候，较多的时候我们会用 command > file 2>&1 这样的写法。说明：本文所述重定向语法适用于大多数 Bourne Again Shell (Bash) 及兼容的 POSIX Shell 环境。
1. iptables 防火墙简介Netfilter/iptables（以下简称 iptables）是 Unix/Linux 系统自带的优秀且完全免费的基于包过滤的防火墙工具。它的功能十分强大、使用非常灵活，可以对流入、流出及流经服务器的数据包进行精细的控制。特别是它可以在低配置环境下运行良好，提供数百台机器的办公上网共享服务丝毫不逊色于数万的企业级专业路由器防火墙。iptables 是 Linux 2.4 及 2.6 内核中集成的服务，其功能与安全性比老一辈的 ipfwadm、ipchains 强大得多。一般认为 iptables 工作在 OSI 七层模型的第二层、第三层和第四层。2. iptables 名词和术语不少刚接触到 iptables 的初学者会被相关名词搞得很晕，不知道其所云的具体意思。本章不会像某些书籍一样长篇大论，而是以最基本的概念让大家容易理解。2.1 什么是容器容器就是装东西的（如箱、包、坛等），用来包装或转载物品的存寄器。在 iptables 里，就是用来描述这种包含或者说术语的关系。下面我们就来说说这些包含关系。2.2 什么是 Netfilter/iptablesNetfilter 是表（tables）的容器。这样解释大家肯定还晕，举例来说：如果把 Netfilter 看成某小区的一栋大楼，那么表（tables）就是楼里的其中一套房子。这套房子（tables）就属于这栋楼"Netfilter"。比如，把你们家所在的小区所在楼当成 Netfilter。2.3 什么是表（tables）表（tables）是链的容器，及所有的链（chains）都是属于表（tables）。如上，如果把 Netfilter 看成某个小区的一栋楼，那么表（tables）就是楼里的某一套房子，比如你们家住的房子。当然了，表（tables）可能不止一个！2.4 什么是链（chains）链（chains）是规则（Rules）的容器，链属于表。接上，如果表（tables）当做一套房子，那么链（chains）可以说是房子里的家具（桌子、柜子等）。每一套房子都可能会有桌子柜子等。2.5 什么是规则（Policy/Rules）规则（Policy）就比较容易理解了，柜子（Policy）属于链（chains）。就是 iptables 一系列过滤信息和具体操作方法，可以理解为购买什么家具，并且如何摆放，设计得更符合要求等。2.6 术语关系整理描述完了基本术语，我们画个形象的表格帮我们理解记忆：Netfilter/iptables表（tables）链（chains）规则（Policy）小区的一栋楼楼里的房子房子里的柜子增加、摆放的规则3. iptables 工作流程前面的介绍已经提到，iptables 是采取数据包过滤机制工作的，所以它会对请求的数据包的包头数据进行分析，并根据我们预先设定的规则进行匹配，来决定是否可以进入、流出、流经主机。下面我们以 iptables 进入主机进行过滤的流程图为例子：提示：防火墙规则的执行顺序认为从前到后依次执行，遇到匹配的规则就不再继续向下检查。如果遇到不匹配的规则则会继续向下进行。重点： 匹配上了拒绝规则也是匹配，因此不再继续向下进行，这一点大家要注意。例如：同时执行了以下规则[root@Nagios2 ~]# iptables -A INPUT -p tcp --dport 3306 -j DROP [root@Nagios2 ~]# iptables -A INPUT -p tcp --dport 3306 -j ACCEPT此时通过命令 telnet 192.168.132.194 3306 检查 3306 是不通的。原因就是 telnet 请求已匹配上了拒绝规则，因此不会再找下面的允许规则。如果需要 telnet 192.168.132.194 3306 连通，可以把 ACCEPT 规则中的 -A 改为 -I，即为 iptables -I INPUT -p tcp --dport 3306 -j ACCEPT，把允许规则放在 INPUT 链的第一行生效。再来一个流程的图例：此例来自于交换机中 ACL 过滤，如 iptables 流程几乎一模一样。入站数据包流程图：出站数据包：小提示： 对于交换机的 ACL 数据包过滤，除了顺序匹配外，还有深度匹配，即读完所有的再去匹配，匹配最精确的规则。4. iptables 的表（tables）和链（chains）描述完 iptables 术语后，相信大家对 iptables 的表和链有了初步的了解。默认情况下，iptables 根据功能和表的定义划分，最常用的有三个表，分别是 filter、nat、mangle。其中每个表又有各自包含不同的操作链（chains）。小提示： 如果你执行 man iptables 可以发现还有 raw 表，不过我们不用理会它，平时我们几乎用不到。下面的表格展示了表和链的对应关系。特殊提示： 这里我们画个思维导图辅助记忆。4.1 iptables 的 filter 表介绍表名说明Filter 表和主机自身相关，负责防火墙（过滤本机流入、流出数据包）。是 iptables 默认使用的表，这个表定义了三个链（chains）说明如下：INPUT负责过滤所有目标地址是主机（防火墙）地址的数据包。通俗的讲，就是过滤进入主机的数据包。FORWARD负责转发流经主机但不进入本机的数据包，起转发作用，和 NAT 表关系很大，后面会详细介绍。OUTPUT处理所有原地址是本机地址的数据包。通俗的讲就是处理从主机发出去的数据包。对于 filter 表的控制是我们实现本机防火墙功能的重要手段。4.2 iptables 的 nat 表介绍表名说明NAT 表是网络地址转换的意思。即负责来源与目的 IP 地址和 Port 的转换，和主机本身无关。一般用于局域网多人共享上网或者内网 IP 映射外网 IP 及不同端口转换服务等功能。NAT 表的功能很重要，这个表定义了三个链（chains）：OUTPUT主机发出去的数据包有关，在数据包路由之前改变主机产生的数据包的目的地址等。PREROUTING在数据包刚到达防火墙时、进行路由判断之前执行的规则，改变包的目的地址（DNAT 功能）、端口等。（通俗比喻：就是收信时，根据规则重写收件人的地址，这看上去不地道啊）。把公司 IP 映射到局域网的机器上，此链多用于把外部 IP 地址端口的服务，映射为内部 IP 地址及端口。POSTROUTING在数据包离开防火墙时进行路由判断之后执行的规则，改变包的源地址（SNAT）、端口等。（通俗比喻：就是寄信时写好发件人的地址，要让人家回信是能够有地址可回）。此链多用于局域网共享上网，把所有局域网的地址，转换为公网地址上。4.3 iptables 的 mangle 表介绍表名说明Mangle主要负责修改数据包中特殊的路由标记，如 TTL、TOS、MARK 等。这个表定义了 5 个链（chains）：INPUT同 filter 表的 INPUTFORWARD同 filter 表的 FORWARDOUTPUT同 filter 表的 OUTPUTPREROUTING同 nat 表的 PREROUTINGPOSTROUTING同 nat 表的 POSTROUTING由于这个表与特殊路由标记有关，一般在生产运维工作中，我们很少用到这个 mangle 表，我们就不做详细介绍了。4.4 iptables 的表与链的工作流程5. iptables 命令规则5.1 iptables 命令帮助信息[root@Nagios2 ~]# iptables -h iptables v1.4.7 Usage: iptables -[AD] chain rule-specification [options] iptables -I chain [rulenum] rule-specification [options] iptables -R chain rulenum rule-specification [options] iptables -D chain rulenum [options] ### 删除规则 iptables -[LS] [chain [rulenum]] [options] iptables -[FZ] [chain] [options] iptables -[NX] chain iptables -E old-chain-name new-chain-name iptables -P chain target [options] #（其中 -P 等同于 –policy 规则） iptables -h (print this help information) Commands: Either long or short options are allowed. 提示：这款请注意。长格式短格式命令都可用，为了打字我们习惯于短格式 --append -A chain Append to chain --delete -D chain Delete matching rule from chain --delete -D chain rulenum Delete rule rulenum (1 = first) from chain --insert -I chain [rulenum] Insert in chain as rulenum (default 1=first) --replace -R chain rulenum Replace rule rulenum (1 = first) in chain --list -L [chain [rulenum]] List the rules in a chain or all chains --list-rules -S [chain [rulenum]] Print the rules in a chain or all chains --flush -F [chain] Delete all rules in chain or all chains --zero -Z [chain [rulenum]] Zero counters in chain or all chains --new -N chain Create a new user-defined chain --delete-chain -X [chain] Delete a user-defined chain --policy -P chain target Change policy on chain to target --rename-chain -E old-chain new-chain Change chain name, (moving any references) Options: [!] --proto -p proto protocol: by number or name, eg. `tcp' [!] --source -s address[/mask][...] source specification [!] --destination -d address[/mask][...] destination specification [!] --in-interface -i input name[+] network interface name ([+] for wildcard) --jump -j target target for rule (may load target extension) --goto -g chain jump to chain with no return --match -m match extended match (may load extension) --numeric -n numeric output of addresses and ports [!] --out-interface -o output name[+] network interface name ([+] for wildcard) --table -t table table to manipulate (default: `filter') --verbose -v verbose mode --line-numbers print line numbers when listing --exact -x expand numbers (display exact values) [!] --fragment -f match second or further fragments only --modprobe=<command> try to insert modules using this command --set-counters PKTS BYTES set the counter during insert/append [!] --version -V print package version. [root@Nagios2 ~]#5.2 实践 iptables 命令规则5.2.1 启动并查看 iptables[root@Nagios2 ~]# /etc/init.d/iptables start iptables: Applying firewall rules: [ OK ] [root@Nagios2 ~]#查看 iptables 的所有链和规则：iptables -L -n # 或者 iptables -L -n -t filter # 或者 iptables -L -n -x -v 中文说明：-L：列出一个或所有链的规则-v：显示详细信息，包括每条规则匹配包数量和匹配字节数-x：在 -v 的基础上，禁止自动单位换算（K,M）-n：只显示 IP 地址和端口号码，不显示域名和服务名称-t：接表名，如果不加 -t，默认就是 -t filter[root@Nagios2 ~]# iptables -L -n Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain FORWARD (policy ACCEPT) target prot opt source destination REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain OUTPUT (policy ACCEPT) target prot opt source destination 默认是 filter 的，没有加 -t 默认查看的就是。如果想要查看 NAT 的表：[root@Nagios2 ~]# iptables -L -n -t nat Chain PREROUTING (policy ACCEPT) target prot opt source destination Chain POSTROUTING (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination [root@Nagios2 ~]#5.2.2 清除默认规则为了从头学起，我们需要首先清除掉所有的默认规则，具体的命令为：iptables -F ### 清除所有规则 iptables -X ### 删除用户自定义的链 iptables -Z ### 链的计数器清零提示： 默认情况下，我们清除规则是对 filter 表的操作，如果是 nat 表，我们需要 iptables -t nat -F。实例演示：[root@Nagios2 ~]# iptables -F [root@Nagios2 ~]# iptables --flush5.2.3 iptables 规则语法1）禁止 SSH 默认的 22 端口[root@Nagios2 ~]# iptables -A INPUT -p tcp --dport 22 -j DROP [root@Nagios2 ~]# iptables -t filter -A INPUT -p tcp --dport 22 -j DROP提示：iptables 默认用的就是 filter 表。因此，以上两条命令等价。其中 INPUT、DROP 等关键词要大写。行为参数 --jump -j target。提示： target 的常见的处理方法有 ACCEPT（接受），DROP（丢弃），REJECT（拒绝）。其中，一般不使用 REJECT 行为，REJECT 会带来安全隐患。在这里我们只需要记住 ACCEPT（接受）、DROP（丢弃）即可。扩展：删除设定的规则iptables -D INPUT -p tcp --dport 22 -j DROP删除规则法二：首先显示规则号：[root@Nagios2 ~]# iptables -L -n --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:21 Chain FORWARD (policy ACCEPT) num target prot opt source destination Chain OUTPUT (policy ACCEPT) num target prot opt source destination [root@Nagios2 ~]# [root@Nagios2 ~]# iptables -D INPUT 1删除规则法三：执行 iptables -F 清除所有的规则，临时生效。重启防火墙或重启计算机失效。删除规则法四：重启防火墙 /etc/init.d/iptables restart。2）禁止 10.0.0.0/24 网段连入iptables -A INPUT -i eth0 -s 10.0.0.0/24 -j DROP iptables -t filter -A INPUT -i eth0 -s 10.0.0.0/24 -j DROP iptables 默认的标记是 filter 表，因此上两条命令等价。3）测试【! 非】源地址不是 192.168.132.201 的禁止连接iptables -A INPUT -i eth1 -s ! 192.168.132.201 -j DROP iptables -A INPUT -i eth1 -s ! 192.168.132.201 -j ACCEPT [root@Nagios2 ~]# iptables -I INPUT -p icmp --icmp-type 8 -i eth0 -s ! 192.168.132.1 -j DROP小提示：这里 -i eth0 表示数据包的进入接口为 eth0，类似的参数还有 -o 匹配数据流出的网络接口。例如：-o eth1 表示数据包的流出接口为 eth1。记忆方法：-in-interface –i [!] input name [+]：Network interface name（[+] for wildcard）-out-interface –o [!] output name [+]：Network interface name ([+] for wildcard)源地址不是 192.168.132.0/24 的禁止连接iptables -A INPUT -s ! 192.168.132.0/24 -j DROP等价于：iptables -t filter -I INPUT -i eth0 -s ! 192.168.132.0/24 -j DROP封掉 3306 端口iptables -A INPUT -p tcp --dport 3306 -j DROP匹配指定协议iptables -A INPUT -p tcp iptables -A INPUT -p udp小提示： -p 参数可以匹配协议名或者协议号。--proto –p [!] proto。The specified protocol can be one of tcp, udp, icmp, or all.匹配协议外的所有协议iptables -A INPUT -p ! tcp匹配主机iptables -A INPUT -s 192.168.132.10 iptables -A INPUT -s ! 192.168.132.10匹配网段（略，参考上文）匹配端口之外的端口iptables -A INPUT -p tcp --dport ! 22 -j DROP匹配端口范围iptables -A INPUT -p tcp --sport 22:80 ### 源端口的 22 和 80 端口就是来访主机的端口 iptables -A INPUT -p tcp -m multiport --dport 21,22,23,24 -j ACCEPT ### 目的端口，就是本地端口 iptables -I INPUT -p tcp --dport 3306:8809 -j ACCEPT匹配 ICMP 端口和 ICMP 类型iptables -A INPUT -p icmp --icmp-type 8 # 例如： iptables -A INPUT -p icmp --icmp-type 8 -j DROP iptables -A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT iptables -A FORWARD -s 192.168.132.0/24 -p icmp -m icmp --icmp-type any -j ACCEPT匹配指定的网络接口iptables -A INPUT -i eth0 ### 进入端口的数据包 iptables -A INPUT -o eth1 ### 流出的端口数据包安全保护Syn-flood protection:[root@Nagios4 ~]# iptables -A FORWARD -p tcp --syn -m limit --limit 1/s -j ACCEPTFurtive port scanner:[root@Nagios4 ~]# iptables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST RST -m limit --limit 1/s -j ACCEPTPing of death:[root@Nagios4 ~]# iptables -A FORWARD -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT5.2.4 生产环境常用服务的 iptables 规则实践特别提示： 以下规则是以默认规则为拒绝规则设计。1）仅允许内部合法的 IP 访问服务器# setting access rules # one, ip access rules, allow all the ips of iptables -A INPUT -s 192.168.132.0/24 -p all -j ACCEPT iptables -A INPUT -s 120.42.60.51/27 -p all -j ACCEPT iptables -A INPUT -s 192.168.1.0/24 -p all -j ACCEPT iptables -A INPUT -s 172.16.20.0/24 -p all -j ACCEPT2）仅允许内部合法的 IP 段访问监控服务 Nagios# second, port access rules # nagios iptables -A INPUT -s 192.168.132.0/24 -p tcp --dport 5666 -j ACCEPT iptables -A INPUT -s 200.100.59.0/24 -p tcp --dport 5666 -j ACCEPT3）仅允许内部合法 IP 段访问 MySQL 数据库和 Oracle 数据库# db iptables -A INPUT -s 192.168.10.0/24 -p tcp --dport 3306 -j ACCEPT iptables -A INPUT -s 192.168.50.0/24 -p tcp --dport 1521 -j ACCEPT4）仅允许内部合法 IP 段访问 SSH 远程连接服务# ssh difference from other servers here…… iptables -A INPUT -p tcp -s 192.168.10.0/24 --dport 50718 -j ACCEPT5）对 HTTP 服务的不同限制a、对外提供 HTTP 服务的业务，要允许 HTTP 服务通过，并且不限制 IP# http iptables -A INPUT -p tcp --dport 80 -j ACCEPTb、对内部提供 HTTP 服务的业务，一般用特殊端口，并且限制合法 IP 连接或者 VPN 连接iptables -A INPUT -s 192.168.132.0 -p tcp -m multiport --dport 8080,8081,8082,8888 -j ACCEPT6）SNMP 的限制iptables -A INPUT -s 192.168.132.0/24 -p UDP --dport 161 -j ACCEPT iptables -A INPUT -s 192.168.155.0/24 -p UDP --dport 161 -j ACCEPT iptables -A INPUT -s 192.168.166.0/24 -p UDP --dport 161 -j ACCEPT7）Rsync 服务的限制策略# rsync iptables -A INPUT -s 192.168.132.0/24 -p tcp -m tcp --dport 873 -j ACCEPT iptables -A INPUT -s 192.168.12.0/24 -p tcp -m tcp --dport 873 -j ACCEPT iptables -A INPUT -s 192.168.1.0/24 -p tcp -m tcp --dport 873 -j ACCEPT8）NFS 服务的限制iptables -A INPUT -s 192.168.10.0/24 -p TCP -m multiport --dport 111,892,2049 -j ACCEPT iptables -A INPUT -s 192.168.100.0/24 -p TCP -m multiport --dport 111,892,2049 -j ACCEPT9）FTP 服务的限制iptables -A INPUT -p tcp --dport 21 -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT提示： 上面内容表示对已经建立连接的数据包，或者发出去的数据包允许通过。说明： iptables -A INPUT -m state --state 状态 -j ACCEPT下面几种状态：INVALID：不可用的。数据包不能被识别，比如说内存溢出，ICMP 错误。ESTABLISHED：这个包已经建立了连接。NEW：这个包已经开始新连接了，然后也是有双向的，但是还看不见。RELATED：这个包开始了一个新连接，并且和已经存在的连接已经有关联。10）ICMP 协议的限制iptables -A INPUT -p icmp --icmp-type any -j ACCEPT iptables -A INPUT -p icmp -s 192.168.10.0/24 -m icmp --icmp-type any -j ACCEPT iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT提示： 如果不想开，就不执行此行。说明： 本文基于 iptables v1.4.7 及 CentOS 6 时代环境编写（使用 /etc/init.d/iptables 服务管理）。现代 Linux 发行版（如 CentOS 7+/Ubuntu 16.04+）多使用 systemctl 管理防火墙服务，且部分新内核已逐步转向 nftables。命令语法基本通用，但服务启动方式及部分模块可能有所不同，请根据实际系统版本调整。
一条语句查看web日志排名前十的IP/URL页面及总数
一条语句查看 Web 日志排名前十的 IP/URL 页面及总数通过分析 Nginx Web 访问日志，我们可以快速统计访问量前十的 IP 地址以及热门页面地址。这有助于识别网站受欢迎的内容，以及排查高流量 IP 的访问行为（例如是否存在恶意爬虫或攻击）。以下命令基于标准的 Linux shell 工具链（awk、sort、uniq、head），适用于大多数 Nginx/Apache 访问日志分析场景。统计访问量前十的 IP 地址该命令用于提取日志中所有请求的源 IP 地址，统计出现频率并展示排名前十的结果。awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -n 10执行结果示例： 391942 211.**.151.218 269168 218.**.103.140 142282 112.**.20.133 105460 112.**.25.241 96016 119.**.155.137 89926 112.**.31.16 83113 218.**.200.19 79975 112.**.28.11 79890 119.**.156.14 72041 124.**.110.72统计特定日期的访问 IP 排名如果需要分析某一天的访问情况，可以先通过 grep 过滤日期，再进行统计。请注意，日志中的日期格式需与实际日志文件一致（例如 07/Nov/2013）。grep "07/Nov/2013" access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 10执行结果示例： 3452 60.**.235.88 2297 119.**.195.228 2258 113.**.145.91 1221 123.**.187.223 905 211.**.82.175 899 117.**.5.207 888 112.**.24.202 787 110.**.139.183 576 143.**.5.44 574 125.**.18.21统计访问量前十的 URL 页面通过提取日志中的特定字段（通常为 Referer 或请求 URL），可以分析哪些页面最受欢迎。在常见的 Combined 日志格式中，$11 通常对应 Referer 字段（即用户来源页面），具体字段索引需根据实际日志格式调整。awk '{print $11}' access.log | sort | uniq -c | sort -nr | head -n 10执行结果示例：1420617 "http://***.com/forum.php" 844547 "http://***.com/?fromuid=1180" 760045 "http://www.***.com/" 697515 "http://www.***.com/forum.php" 436309 "http://www.***.com/?fromuid=1080" 279609 "http://www.***.com/?fromuid=21" 172784 "http://www.***.com/?fromuid=2123" 108563 "http://www.***.com/?fromuid=1090" 86906 "http://www.***.com/plugin.php?id=sanree_brand:sanree_brand" 80661 "http://www.***.com/plugin.php?id=sanree_brand_goods:sanree_brand_goods"说明命令逻辑修正：在使用 uniq -c 统计次数前，必须先通过 sort 将相同内容排列在一起，否则 uniq 只能统计相邻的重复行。上述命令已确保包含 sort 步骤。日志格式差异：awk '{print $11}' 中的字段索引 $11 取决于 Nginx/Apache 的日志格式配置（log_format）。如果是标准 Combined 格式，$11 通常为 Referer；若需统计请求的 URL 路径，通常对应 $7 或其他字段，建议使用 head -1 access.log 查看字段分布。时效性：文中示例日期（2013 年）较早，实际操作时请替换为当前日志中的日期格式。
NC (Netcat) 使用指南第一次知道 nc（Netcat）是好几年前的事了，那个时候天比现在更蓝，草比现在更绿，卤煮也还是一个刚上大学不久的青葱骚年...现在把这个 01 年的老古董拿出来说好像有点炒冷饭的意思，资料也铺天盖地了，说多了还惹人嫌。不过没办法啊，毕竟当卤煮逐渐发现它有许多颇有内涵的功能的时候，连小伙伴都让它给惊呆了 :D ...由于对 Hacker 文化的兴趣，顺理成章的在合适的时候发现了 nc。但当时的卤煮正值被铺天盖地的计算机方方面面知识砸得晕晕乎乎的人生阶段... 对于这个一直享有“瑞士军刀”之名的软件，找过来看了两把完全没有发现其中的滚滚内涵。因为是这样的，nc 提供的基本功能并不多，Windows 版本的 prompt> nc -h 所给出的所谓官方操作文档不过下面这寥寥数语：[v1.11 NT www.vulnwatch.org/netcat/] connect to somewhere: nc [-options] hostname port[s] [p listen for inbound: nc -l -p port [options] [hostname options: -d detach from console, background m -e prog inbound program to exec [dangerou -g gateway source-routing hop point[s], up t -G num source-routing pointer: 4, 8, 12, -h this cruft -i secs delay interval for lines sent, po -l listen mode, for inbound connects -L listen harder, re-listen on socke -n numeric-only IP addresses, no DNS -o file hex dump of traffic -p port local port number -r randomize local and remote ports -s addr local source address -t answer TELNET negotiation -u UDP mode -v verbose [use twice to be more ver -w secs timeout for connects and final ne -z zero-I/O mode [used for scanning] port numbers can be individual or ranges: m-n [inclusive]参数详解-d：字面上大约是后台运行的意思，不占用当前 Shell。-e：这个参数大有内涵。网上一般翻译成程序重定向什么的，但是“重定向”这个词... 卤煮暂时没去看源码，不知道其具体做了什么事情，但猜测可能是 fork 一个子进程 exec 用户程序，然后父进程 wait 子进程的做法。这个 -e 用在 Server 端的时候，目测是在 accept 成功之后，然后把这个用户程序的输入输出重定向到 Client Socket 对应的 readfd 和 writefd 上。具体应用下面再说。-g：额... 这个参数从文档字面上看不太懂（一堆英文专业术语冒出来好可怕... :X)。有些同学翻译成“源路由跳点”实在是太翻译软件了。看下资料大致上是为了做到强制数据流经这些节点，这样就有机会做到类似利用节点 IP 巧妙的避开目标机的 ACL 限制这样的功能。官文用了一个词叫"loose-source-routed path"，loose 猜测意思大约是指仅简单的保证会经过 -g 给的节点，并不能保证以这条列表的所示路径来完成链路的意思，因为路由发包所选择的下一个 hop 是有自己的路由策略的吧，所以保证不了两个路由间的具体路径。各个操作系统的版本不一定都支持 -g，至于数据是否会依照所给节点顺序来传递现在也还不是很清楚。-G：在 -g 所给列表中选择一个地址作为当前的下一个路由。据说是因为 IP 的内存对象都是 4 字节存储，所以 -G 所给参数也得是 4 的倍数，虽然自己换算下也没什么但这种设计只能用惨无人道来形容。这个功能据说在“网络诈骗”方面有一定用武之地，这个卤煮暂时没感受。（CentOS 装了个版本没有 -g 和 -G）-h：For help。-i：每行数据发送出去的延时时间吧（秒）。-l：会 listen 的东东我们都管他叫 Server... 理解成 Server Mode 就好，一次性行为（怎么读都可以...）。-L：和 -l 做的事情一样，区别在这个"re"上，客户端端口后依然保持。-n：表示 connect 的目标地址是一个 IP，可以略过 DNS 的过程。这个参数一般情况下加不加好像无所谓吧，不加 -n 也可以直接用 IP 的。-o：把往来数据 log 起来，监控用。-p：后面跟端口号，指定一个端口。-r：在范围扫描端口的时候，即执行类似 nc -v 127.0.0.1 1-1000 这样的操作时，加上 -r 参数可以使每次扫描的端口号随机化，否则一般是从高到低依序扫描。-s：翻译过来叫 nc 建立连接时用的 IP 地址，据说可以仿冒本地地址，暂时还不太知道怎么使用。-t：以 Telnet 方式进行交互，这个暂时不知道和普通的字符方式有何区别。-u：UDP Mode，比如扫端口的时候，带 -u 扫 UDP 端口，不带就默认扫 TCP 端口。-v：提示信息，连着两个 -v 会得到更加详细的提示信息，具体区别实际用的时候自己去感受下。-w：Connect 时的超时时间，一般扫描端口的时候会加上。-z：关闭 IO，就是英文字面上的意思，用途也说了。无意在文档泛滥成灾的网络上再加一篇劣质文档，总之就是先总结下感受这样子，有助卤煮记忆。如这些参数所见，除了暂时看上去显得很高端大气上档次的 -g -G 组合之外，nc 实际上只作了很少的事情。无非是每个学过网络编程的人都反反复复写过的小例子，像什么 connect、listen、bind、read、write、execxx、sleep、IO 重定向之类的组合一下。对于思路不够开阔的人（比如卤煮）来说，好像这样的软件根本做不了什么事情。说起来反而是自己以前把网络中用到的许多功能自己给想太复杂了，总觉得要做到某个具体的功能是需要为此写份代码，于是这事那事的一拖再拖最后啥事儿也没去干。前几天要重新学习下编写 Server，刚准备动手写个 Client 以便测试的时候突然灵感一闪，想起了阔别已久的 nc，然后便一发不可收拾的用了下去。发现它能做到的事情实在是很多，真是印证了那句话，“简单即是美好”。扫描端口nc -nvvz -w2 127.0.0.1 1-1000最基本的功能之一了，扫描目标 IP 的端口段，然后用 -w 加上一个超时时间。参数里加上 -r 使得端口扫描变得随机一些，对方 Log 里看起来不那么像是被扫描的（其实还是很容易看出来）。连接到目标nc -nvv 127.0.0.1 8089作为测试客户端非常常用的功能，即连接到目标 IP 的某个端口上。连接上之后会把 stdin 的数据发给 Server。在确定服务端正常开启的情况下连接被拒，多半要去检查下防火墙设置。服务器nc -lvv -p 8089让 nc 作为一个 Server 监听 8089 端口，把 stdin 的数据发给 Client。传文件其实和 nc 没啥关系，主要利用了系统 IO 重定向或 Pipe 的功能，标准化带来的好处多多。发送端通过 Pipe 把文件数据传递给 stdin，或者通过重定向把 stdin 重定向到某个文件上，然后接受端只要对 stdout 进行重定向把它定向到目标文件上去即可。比如：# Server 端 nc -lvv -p 8089 < my.txt # Client 端 nc -nvv serverip 8089 > my.txt当你在纠结两台机子传个文件到底是用网络共享好还是利用中间 FTP 好，又或者是 scp 甚至于给自己发个邮件另一台机子上去下载的时候，这简直是你的诺亚方舟。这个功能说成传文件说的有些狭隘了，总之就是传递数据。理论上可以把接收端的数据（可能是某种格式编码的）直接传给对应的处理软件去执行，举个例子比如说看个视频什么的。得到对端机子的 Shell-e 的邪恶能力终于派上用场了。nc -lvv -p 8089 -t -e cmd.exe然后你去连它的时候神奇的事情发生了，自己的终端会变成对方机子的终端，可以随便做邪恶的事情（在用户权限范围下），原理的话估计和我上面写的类似。如果对方机子的 nc 支持 -d，那就更理想一点，加上这个参数使得 nc 后台运行更加难以发现。更进一步的，修改 nc.exe 的文件名使查看进程的时候别人也不容易起疑。这个 NB 的功能除了拿来动真刀真枪之外，还能怎么玩其实主要看 -e 后面的这个用户程序怎么去写吧，这个还是自己去找灵感。蜜罐还是作为 Server 的基本功能来说，思路不一样会发现用途立刻就大相径庭。比如说你没事儿 listen 一个端口，然后 -o 来打 Log，闲来喝喝茶翻翻 Log，你就能知道都有哪些人对你的这个端口感兴趣（嘿嘿嘿...）更进一步的，因为许多小坏蛋们总是用一些安全渗透工具来找空子，毕竟是工具嘛，识别一些漏洞总是有一组傻乎乎的规则的。如果想要捉弄他们让他们误以为真的有漏洞而采取进一步措施的话，可以结合 nc 的数据传输功能和 -e 再加上自写程序来伪造一个。不过小心玩火自焚喽。反向链接前面说一个拿到 Server Shell 的情况，要求 Server 端绑定一个 Shell 程序到端口上。那么实际应用情境中，一般就是小坏蛋给小绵羊电脑上种一个 Server 啦，然后每次小坏蛋去连接都能得到 Shell。但由于种种问题，小坏蛋要直接连到小绵羊电脑可能比较麻烦，尤其是小绵羊电脑在一个局域网中，通过 NAT 上网的情况下。为了解决这个问题，可以换一种思路：小绵羊作为主动发起端并自己绑定一个 Shell 程序到 nc。要实现这个步骤可能是小坏蛋哪天千辛万苦入了小绵羊电脑设了一个计划任务，也可能是小坏蛋到小绵羊家里玩的时候在小绵羊电脑里使了个坏... 然后小坏蛋电脑只需要安心开着 Server 等小绵羊上钩，每次小绵羊连接到 Server 就自动把自己卖了出去...原理的话，猜测是在阻塞 connect 成功之后 fork 然后 exec，再 wait，和主动连接差不多的。不过反向链接省去了正面突围的大麻烦，功德一件，善哉善哉。总结看到比较典型的大概有上面这几种，熟悉的人肯定已经熟悉的不能再熟了。通过和各个系统工具的组合想必还有更多好玩的用法的，所以 nc 可是一个很益智的玩具呢。当然它不仅仅是玩具，必要时也可能帮你解决实际问题。综合来看，nc 在许多方面的功能都有比较好用的独立软件，这也侧面反应了 nc 在这些功能上都有许多做的不够好的地方（没有足够多针对性的优化和扩展功能）。但它胜在麻雀虽小但五脏俱全，这一点十分讨人喜欢，在许多没有特定工作环境的场合会是非常有吸引力的选择。虽然还没有深度使用这个工具，不过有预感结合自编脚本来用的话，应该会表现的更加出彩一些。就像语言影响人的思维面一样，工具的掌握让人更加清晰的认识到何事可为何事不可为，从而进一步影响决策和行为。为了进化而努力。说明：本文基于早期 Netcat 版本（如 v1.11 NT）编写。现代 Linux 发行版默认安装的通常是 netcat-openbsd 或 nmap 套件中的 ncat，出于安全考虑，部分新版本可能移除了 -e 参数（执行程序功能）。如需使用相关功能，请确认当前安装的具体版本及特性。
编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。Systemd 是 Linux 系统工具，用来启动守护进程，已成为大多数发行版的标准配置。本文介绍它的基本用法，分为上下两篇。今天介绍它的主要命令，下一篇介绍如何用于实战。一、由来历史上，Linux 的启动一直采用 init 进程。下面的命令用来启动服务：$ sudo /etc/init.d/apache2 start # 或者 $ service apache2 start这种方法有两个主要缺点：启动时间长：init 进程是串行启动，只有前一个进程启动完，才会启动下一个进程。启动脚本复杂：init 进程只是执行启动脚本，不管其他事情。脚本需要自己处理各种情况，这往往使得脚本变得很长。二、Systemd 概述Systemd 就是为了解决这些问题而诞生的。它的设计目标是，为系统的启动和管理提供一套完整的解决方案。根据 Linux 惯例，字母 d 是守护进程（daemon）的缩写。Systemd 这个名字的含义，就是它要守护整个系统。（上图为 Systemd 作者 Lennart Poettering）使用了 Systemd，就不需要再用 init 了。Systemd 取代了 initd，成为系统的第一个进程（PID 等于 1），其他进程都是它的子进程。$ systemctl --version上面的命令查看 Systemd 的版本。Systemd 的优点是功能强大，使用方便；缺点是体系庞大，非常复杂。事实上，现在还有很多人反对使用 Systemd，理由就是它过于复杂，与操作系统的其他部分强耦合，违反"keep simple, keep stupid"的 Unix 哲学。（上图为 Systemd 架构图）三、系统管理Systemd 并不是一个命令，而是一组命令，涉及到系统管理的方方面面。3.1 systemctlsystemctl 是 Systemd 的主命令，用于管理系统。# 重启系统 $ sudo systemctl reboot # 关闭系统，切断电源 $ sudo systemctl poweroff # CPU 停止工作 $ sudo systemctl halt # 暂停系统 $ sudo systemctl suspend # 让系统进入冬眠状态 $ sudo systemctl hibernate # 让系统进入交互式休眠状态 $ sudo systemctl hybrid-sleep # 启动进入救援状态（单用户状态） $ sudo systemctl rescue3.2 systemd-analyzesystemd-analyze 命令用于查看启动耗时。# 查看启动耗时 $ systemd-analyze # 查看每个服务的启动耗时 $ systemd-analyze blame # 显示瀑布状的启动过程流 $ systemd-analyze critical-chain # 显示指定服务的启动流 $ systemd-analyze critical-chain atd.service3.3 hostnamectlhostnamectl 命令用于查看当前主机的信息。# 显示当前主机的信息 $ hostnamectl # 设置主机名 $ sudo hostnamectl set-hostname rhel73.4 localectllocalectl 命令用于查看本地化设置。# 查看本地化设置 $ localectl # 设置本地化参数 $ sudo localectl set-locale LANG=en_GB.utf8 $ sudo localectl set-keymap en_GB3.5 timedatectltimedatectl 命令用于查看当前时区设置。# 查看当前时区设置 $ timedatectl # 显示所有可用的时区 $ timedatectl list-timezones # 设置当前时区 $ sudo timedatectl set-timezone America/New_York $ sudo timedatectl set-time YYYY-MM-DD $ sudo timedatectl set-time HH:MM:SS3.6 loginctlloginctl 命令用于查看当前登录的用户。# 列出当前 session $ loginctl list-sessions # 列出当前登录用户 $ loginctl list-users # 列出显示指定用户的信息 $ loginctl show-user ruanyf四、Unit4.1 含义Systemd 可以管理所有系统资源。不同的资源统称为 Unit（单位）。Unit 一共分成 12 种：Service unit：系统服务Target unit：多个 Unit 构成的一个组Device Unit：硬件设备Mount Unit：文件系统的挂载点Automount Unit：自动挂载点Path Unit：文件或路径Scope Unit：不是由 Systemd 启动的外部进程Slice Unit：进程组Snapshot Unit：Systemd 快照，可以切回某个快照Socket Unit：进程间通信的 socketSwap Unit：swap 文件Timer Unit：定时器systemctl list-units 命令可以查看当前系统的所有 Unit。# 列出正在运行的 Unit $ systemctl list-units # 列出所有 Unit，包括没有找到配置文件的或者启动失败的 $ systemctl list-units --all # 列出所有没有运行的 Unit $ systemctl list-units --all --state=inactive # 列出所有加载失败的 Unit $ systemctl list-units --failed # 列出所有正在运行的、类型为 service 的 Unit $ systemctl list-units --type=service4.2 Unit 的状态systemctl status 命令用于查看系统状态和单个 Unit 的状态。# 显示系统状态 $ systemctl status # 显示单个 Unit 的状态 $ systemctl status bluetooth.service # 显示远程主机的某个 Unit 的状态 $ systemctl -H root@rhel7.example.com status httpd.service除了 status 命令，systemctl 还提供了三个查询状态的简单方法，主要供脚本内部的判断语句使用。# 显示某个 Unit 是否正在运行 $ systemctl is-active application.service # 显示某个 Unit 是否处于启动失败状态 $ systemctl is-failed application.service # 显示某个 Unit 服务是否建立了启动链接 $ systemctl is-enabled application.service4.3 Unit 管理对于用户来说，最常用的是下面这些命令，用于启动和停止 Unit（主要是 service）。# 立即启动一个服务 $ sudo systemctl start apache.service # 立即停止一个服务 $ sudo systemctl stop apache.service # 重启一个服务 $ sudo systemctl restart apache.service # 杀死一个服务的所有子进程 $ sudo systemctl kill apache.service # 重新加载一个服务的配置文件 $ sudo systemctl reload apache.service # 重载所有修改过的配置文件 $ sudo systemctl daemon-reload # 显示某个 Unit 的所有底层参数 $ systemctl show httpd.service # 显示某个 Unit 的指定属性的值 $ systemctl show -p CPUShares httpd.service # 设置某个 Unit 的指定属性 $ sudo systemctl set-property httpd.service CPUShares=5004.4 依赖关系Unit 之间存在依赖关系：A 依赖于 B，就意味着 Systemd 在启动 A 的时候，同时会去启动 B。systemctl list-dependencies 命令列出一个 Unit 的所有依赖。$ systemctl list-dependencies nginx.service上面命令的输出结果之中，有些依赖是 Target 类型（详见下文），默认不会展开显示。如果要展开 Target，就需要使用 --all 参数。$ systemctl list-dependencies --all nginx.service五、Unit 的配置文件5.1 概述每一个 Unit 都有一个配置文件，告诉 Systemd 怎么启动这个 Unit。Systemd 默认从目录 /etc/systemd/system/ 读取配置文件。但是，里面存放的大部分文件都是符号链接，指向目录 /usr/lib/systemd/system/，真正的配置文件存放在那个目录。systemctl enable 命令用于在上面两个目录之间，建立符号链接关系。$ sudo systemctl enable clamd@scan.service # 等同于 $ sudo ln -s '/usr/lib/systemd/system/clamd@scan.service' '/etc/systemd/system/multi-user.target.wants/clamd@scan.service'如果配置文件里面设置了开机启动，systemctl enable 命令相当于激活开机启动。与之对应的，systemctl disable 命令用于在两个目录之间，撤销符号链接关系，相当于撤销开机启动。$ sudo systemctl disable clamd@scan.service配置文件的后缀名，就是该 Unit 的种类，比如 sshd.socket。如果省略，Systemd 默认后缀名为 .service，所以 sshd 会被理解成 sshd.service。5.2 配置文件的状态systemctl list-unit-files 命令用于列出所有配置文件。# 列出所有配置文件 $ systemctl list-unit-files # 列出指定类型的配置文件 $ systemctl list-unit-files --type=service这个命令会输出一个列表。$ systemctl list-unit-files UNIT FILE STATE chronyd.service enabled clamd@.service static clamd@scan.service disabled这个列表显示每个配置文件的状态，一共有四种：enabled：已建立启动链接disabled：没建立启动链接static：该配置文件没有 [Install] 部分（无法执行），只能作为其他配置文件的依赖masked：该配置文件被禁止建立启动链接注意，从配置文件的状态无法看出，该 Unit 是否正在运行。这必须执行前面提到的 systemctl status 命令。$ systemctl status bluetooth.service一旦修改配置文件，就要让 Systemd 重新加载配置文件，然后重新启动，否则修改不会生效。$ sudo systemctl daemon-reload $ sudo systemctl restart httpd.service5.3 配置文件的格式配置文件就是普通的文本文件，可以用文本编辑器打开。systemctl cat 命令可以查看配置文件的内容。$ systemctl cat atd.service [Unit] Description=ATD daemon [Service] Type=forking ExecStart=/usr/bin/atd [Install] WantedBy=multi-user.target从上面的输出可以看到，配置文件分成几个区块。每个区块的第一行，是用方括号表示的区别名，比如 [Unit]。注意，配置文件的区块名和字段名，都是大小写敏感的。每个区块内部是一些等号连接的键值对。[Section] Directive1=value Directive2=value . . .注意，键值对的等号两侧不能有空格。5.4 配置文件的区块[Unit] 区块通常是配置文件的第一个区块，用来定义 Unit 的元数据，以及配置与其他 Unit 的关系。它的主要字段如下：Description：简短描述Documentation：文档地址Requires：当前 Unit 依赖的其他 Unit，如果它们没有运行，当前 Unit 会启动失败Wants：与当前 Unit 配合的其他 Unit，如果它们没有运行，当前 Unit 不会启动失败BindsTo：与 Requires 类似，它指定的 Unit 如果退出，会导致当前 Unit 停止运行Before：如果该字段指定的 Unit 也要启动，那么必须在当前 Unit 之后启动After：如果该字段指定的 Unit 也要启动，那么必须在当前 Unit 之前启动Conflicts：这里指定的 Unit 不能与当前 Unit 同时运行Condition...：当前 Unit 运行必须满足的条件，否则不会运行Assert...：当前 Unit 运行必须满足的条件，否则会报启动失败[Install] 通常是配置文件的最后一个区块，用来定义如何启动，以及是否开机启动。它的主要字段如下：WantedBy：它的值是一个或多个 Target，当前 Unit 激活时（enable）符号链接会放入 /etc/systemd/system 目录下面以 Target 名 + .wants 后缀构成的子目录中RequiredBy：它的值是一个或多个 Target，当前 Unit 激活时，符号链接会放入 /etc/systemd/system 目录下面以 Target 名 + .required 后缀构成的子目录中Alias：当前 Unit 可用于启动的别名Also：当前 Unit 激活（enable）时，会被同时激活的其他 Unit[Service] 区块用来 Service 的配置，只有 Service 类型的 Unit 才有这个区块。它的主要字段如下：Type：定义启动时的进程行为。它有以下几种值。Type=simple：默认值，执行 ExecStart 指定的命令，启动主进程Type=forking：以 fork 方式从父进程创建子进程，创建后父进程会立即退出Type=oneshot：一次性进程，Systemd 会等当前服务退出，再继续往下执行Type=dbus：当前服务通过 D-Bus 启动Type=notify：当前服务启动完毕，会通知 Systemd，再继续往下执行Type=idle：若有其他任务执行完毕，当前服务才会运行ExecStart：启动当前服务的命令ExecStartPre：启动当前服务之前执行的命令ExecStartPost：启动当前服务之后执行的命令ExecReload：重启当前服务时执行的命令ExecStop：停止当前服务时执行的命令ExecStopPost：停止当其服务之后执行的命令RestartSec：自动重启当前服务间隔的秒数Restart：定义何种情况 Systemd 会自动重启当前服务，可能的值包括 always（总是重启）、on-success、on-failure、on-abnormal、on-abort、on-watchdogTimeoutSec：定义 Systemd 停止当前服务之前等待的秒数Environment：指定环境变量Unit 配置文件的完整字段清单，请参考 官方文档。六、Target启动计算机的时候，需要启动大量的 Unit。如果每一次启动，都要一一写明本次启动需要哪些 Unit，显然非常不方便。Systemd 的解决方案就是 Target。简单说，Target 就是一个 Unit 组，包含许多相关的 Unit。启动某个 Target 的时候，Systemd 就会启动里面所有的 Unit。从这个意义上说，Target 这个概念类似于"状态点"，启动某个 Target 就好比启动到某种状态。传统的 init 启动模式里面，有 RunLevel 的概念，跟 Target 的作用很类似。不同的是，RunLevel 是互斥的，不可能多个 RunLevel 同时启动，但是多个 Target 可以同时启动。# 查看当前系统的所有 Target $ systemctl list-unit-files --type=target # 查看一个 Target 包含的所有 Unit $ systemctl list-dependencies multi-user.target # 查看启动时的默认 Target $ systemctl get-default # 设置启动时的默认 Target $ sudo systemctl set-default multi-user.target # 切换 Target 时，默认不关闭前一个 Target 启动的进程， # systemctl isolate 命令改变这种行为， # 关闭前一个 Target 里面所有不属于后一个 Target 的进程 $ sudo systemctl isolate multi-user.targetTarget 与传统 RunLevel 的对应关系如下：Traditional runlevelNew target nameSymbolically linked to...Runlevel 0runlevel0.targetpoweroff.targetRunlevel 1runlevel1.targetrescue.targetRunlevel 2runlevel2.targetmulti-user.targetRunlevel 3runlevel3.targetmulti-user.targetRunlevel 4runlevel4.targetmulti-user.targetRunlevel 5runlevel5.targetgraphical.targetRunlevel 6runlevel6.targetreboot.target它与 init 进程的主要差别如下：默认的 RunLevel（在 /etc/inittab 文件设置）现在被默认的 Target 取代，位置是 /etc/systemd/system/default.target，通常符号链接到 graphical.target（图形界面）或者 multi-user.target（多用户命令行）。启动脚本的位置，以前是 /etc/init.d 目录，符号链接到不同的 RunLevel 目录（比如 /etc/rc3.d、/etc/rc5.d 等），现在则存放在 /lib/systemd/system 和 /etc/systemd/system 目录。配置文件的位置，以前 init 进程的配置文件是 /etc/inittab，各种服务的配置文件存放在 /etc/sysconfig 目录。现在的配置文件主要存放在 /lib/systemd 目录，在 /etc/systemd 目录里面的修改可以覆盖原始设置。七、日志管理Systemd 统一管理所有 Unit 的启动日志。带来的好处就是，可以只用 journalctl 一个命令，查看所有日志（内核日志和应用日志）。日志的配置文件是 /etc/systemd/journald.conf。journalctl 功能强大，用法非常多。# 查看所有日志（默认情况下，只保存本次启动的日志） $ sudo journalctl # 查看内核日志（不显示应用日志） $ sudo journalctl -k # 查看系统本次启动的日志 $ sudo journalctl -b $ sudo journalctl -b -0 # 查看上一次启动的日志（需更改设置） $ sudo journalctl -b -1 # 查看指定时间的日志 $ sudo journalctl --since="2012-10-30 18:17:16" $ sudo journalctl --since "20 min ago" $ sudo journalctl --since yesterday $ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00" $ sudo journalctl --since 09:00 --until "1 hour ago" # 显示尾部的最新 10 行日志 $ sudo journalctl -n # 显示尾部指定行数的日志 $ sudo journalctl -n 20 # 实时滚动显示最新日志 $ sudo journalctl -f # 查看指定服务的日志 $ sudo journalctl /usr/lib/systemd/systemd # 查看指定进程的日志 $ sudo journalctl _PID=1 # 查看某个路径的脚本的日志 $ sudo journalctl /usr/bin/bash # 查看指定用户的日志 $ sudo journalctl _UID=33 --since today # 查看某个 Unit 的日志 $ sudo journalctl -u nginx.service $ sudo journalctl -u nginx.service --since today # 实时滚动显示某个 Unit 的最新日志 $ sudo journalctl -u nginx.service -f # 合并显示多个 Unit 的日志 $ journalctl -u nginx.service -u php-fpm.service --since today # 查看指定优先级（及其以上级别）的日志，共有 8 级 # 0: emerg # 1: alert # 2: crit # 3: err # 4: warning # 5: notice # 6: info # 7: debug $ sudo journalctl -p err -b # 日志默认分页输出，--no-pager 改为正常的标准输出 $ sudo journalctl --no-pager # 以 JSON 格式（单行）输出 $ sudo journalctl -b -u nginx.service -o json # 以 JSON 格式（多行）输出，可读性更好 $ sudo journalctl -b -u nginx.service -o json-pretty # 显示日志占据的硬盘空间 $ sudo journalctl --disk-usage # 指定日志文件占据的最大空间 $ sudo journalctl --vacuum-size=1G # 指定日志文件保存多久 $ sudo journalctl --vacuum-time=1year（完）
Linux 常用命令（基础）本文整理 Linux 常用基础命令，涵盖日常操作及文末的关机/重启相关命令说明。常用命令1. manman (Manual) 命令用于查看命令的帮助文档，对你熟悉或不熟悉的命令提供帮助解释。示例：man ls查看 ls 相关的用法说明。注： 按 q 键或者 Ctrl+C 退出。在 Linux 下可以使用 Ctrl+C 终止当前程序运行。2. lsls (List) 用于查看目录或者文件的属性，列举出任一目录下面的文件。示例：ls /usr/man ls -l输出说明：首字符表示文件类型：d 表示目录 (directory)，- 表示文件，l 表示连接文件 (link)。后续字符表示文件或者目录许可权限：分别用可读 (r)、可写 (w)、可运行 (x) 表示。3. cpcp (Copy) 用于拷贝文件。示例：cp filename1 filename2 # 把 filename1 拷贝成 filename2 cp 1.c netseek/2.c # 将 1.c 拷到 netseek 目录下命名为 2.c4. rmrm (Remove) 用于删除文件和目录。示例：rm 1.c # 将 1.c 这个文件删除5. mvmv (Move) 用于移走目录或者改文件名。示例：mv filename1 filename2 # 将 filename1 改名为 filename2 mv qib.tgz ../qib.tgz # 移到上一级目录6. cd / pwdcd (Change Directory)：改变当前目录。pwd (Print Working Directory)：查看当前所在目录完整路径。示例：pwd # 查看当前所在目录路径 cd netseek # 进入 netseek 这个目录 cd # 退出当前目录（回到用户主目录）7. cat / more将某个文件的内容显示出来。两个命令的区别在于：cat 把文件内容一直打印出来，而 more 则分屏显示。示例：cat > 1.c # 把代码粘贴到 1.c 文件里，按 Ctrl+D 保存代码 cat 1.c # 查看文件内容 more 1.c # 分屏查看文件内容编译相关：gcc -o 1 1.c # 将 1.c 编译成可执行文件8. chmodchmod (Change Mode) 用于修改文件权限。用法：符号模式：chmod [who][+|-|=][mode] filename数字模式：chmod [八进制数] filename示例：chmod u+x filename # 只想给自己运行，别人只能读 chmod g+x filename # 同组的人来执行参数说明：u 表示文件主人 (user)，g 表示文件所在组 (group)，o 表示其他人 (others)。r 表可读，w 表可写，x 表可以运行。9. clear / dateclear：清屏，相当于 DOS 下的 cls。date：显示当前系统时间。10. mountmount 用于加载一个硬件设备。用法：mount [参数] 要加载的设备 载入点示例：mount /dev/cdrom /mnt/cdrom # 加载光盘设备到挂载点 cd /mnt/cdrom # 进入光盘目录11. susu (Switch User) 用于在不退出登录的情况下，切换到另外一个人的身份。用法：su -l 用户名 # 如果用户名缺省，则切换到 root 状态示例：su -l netseek # 切换到 netseek 这个用户，将提示输入密码12. whoami / whereis / which / idwhoami：确认自己身份。whereis：查询命令所在目录以及帮助文档所在目录。which：查询该命令所在目录 (类似 whereis)。id：打印出自己的 UID 以及 GID。(UID: 用户身份唯一标识。GID: 用户组身份唯一标识。每一个用户只能有一个唯一的 UID 和 GID)示例：whoami # 显示你自己登录的用户名 whereis bin # 显示 bin 所在的目录，例如：/usr/local/bin which bin # 查询 bin 命令的路径13. grep / findgrep：文本内容搜索。find：文件或者目录名以及权限属主等匹配搜索。示例：grep success * # 查找当前目录下面所有文件里面含有 success 字符的文件14. kill可以杀死某个正在进行或者已经是僵尸 (Zombie) 状态的进程。示例：ps ax # 查看进程列表，配合 kill 使用15. passwd可以设置或修改用户口令。16. history查看用户用过的命令历史。示例：history # 显示用户过去使用的命令17. !!执行最近一次的命令。18. mkdirmkdir (Make Directory) 用于创建目录。示例：mkdir netseek # 创建 netseek 这个目录19. tartar 用于打包或解压文件。示例：tar -zxvf nmap-3.45.tgz # 将文件解压到当前目录20. finger可以让使用者查询一些其他使用者的资料（注：现代系统默认可能未安装）。示例：finger # 查看所有用户的使用资料 finger root # 查看 root 的资料关机与重启Linux 中常用的关机和重新启动命令有 shutdown、halt、reboot 以及 init。它们都可以达到关机和重新启动的目的，但是每个命令的内部工作过程是不同的，下面将逐一进行介绍。shutdownshutdown 命令用于安全关闭 Linux 系统。有些用户会使用直接断掉电源的方式来关闭 Linux，这是十分危险的。因为 Linux 与 Windows 不同，其后台运行着许多进程，所以强制关机可能会导致进程的数据丢失，使系统处于不稳定的状态，甚至会损坏硬件设备。执行 shutdown 命令时，系统会通知所有登录的用户系统将要关闭，并且 login 指令会被冻结，即新的用户不能再登录系统。使用 shutdown 命令可以直接关闭系统，也可以延迟指定的时间再关闭系统，还可以重新启动。延迟指定的时间再关闭系统，可以让用户有时间储存当前正在处理的文件和关闭已经打开的程序。部分参数如下：[-t]：指定在多长时间之后关闭系统。[-r]：重启系统。[-k]：并不真正关机，只是给每个登录用户发送警告信号。[-h]：关闭系统（halt）。shutdown 命令的工作实质是给 init 程序发送信号（signal），要求其切换系统的运行级别（Runlevel）。系统的运行级别包括：0：关闭系统。1：单用户模式。如果没有为 shutdown 命令指定 -h 或 -r 参数而直接执行，则默认将切换到此运行级别。2：多用户模式（不支持 NFS）。3：多用户模式（支持 NFS），一般常用此种运行级别。5：多用户模式（GUI 模式）。6：重新启动系统。halthalt 是最简单的关机命令，其实际上是调用 shutdown -h 命令。halt 执行时，杀死应用进程，文件系统写操作完成后就会停止内核。部分参数如下：[-f]：没有调用 shutdown 而强制关机或重启。[-i]：关机或重新启动之前，关掉所有的网络接口。[-p]：关机时调用 poweroff，此选项为缺省选项。rebootreboot 的工作过程与 halt 类似，其作用是重新启动，而 halt 是关机。其参数也与 halt 类似。initinit 是所有进程的祖先，其进程号始终为 1。init 用于切换系统的运行级别，切换的工作是立即完成的。init 0：用于立即将系统运行级别切换为 0，即关机。init 6：用于将系统运行级别切换为 6，即重新启动。说明： 本文部分命令（如 init 运行级别、finger）基于传统 SysV init 系统。现代 Linux 发行版（如 CentOS 7+、Ubuntu 16.04+）多采用 systemd 管理初始化进程，部分命令行为或运行级别概念可能有所不同，请以实际发行版文档为准。
编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。半年前，我写了《计算机是如何启动的？》，探讨了 BIOS 和主引导记录的作用。那篇文章不涉及操作系统，只与主板的板载程序有关。今天，我想接着往下写，探讨操作系统接管硬件以后发生的事情，也就是操作系统的启动流程。这个部分比较有意思。因为在 BIOS 阶段，计算机的行为基本上被写死了，程序员可以做的事情并不多；但是，一旦进入操作系统，程序员几乎可以定制所有方面。所以，这个部分与程序员的关系更密切。我主要关心的是 Linux 操作系统，它是目前服务器端的主流操作系统。下面的内容针对的是 Debian 发行版，因为我对其他发行版不够熟悉。1. 加载内核操作系统接管硬件以后，首先读入 /boot 目录下的内核文件。以我的电脑为例，/boot 目录下面大概是这样一些文件：$ ls /boot config-3.2.0-3-amd64 config-3.2.0-4-amd64 grub initrd.img-3.2.0-3-amd64 initrd.img-3.2.0-4-amd64 System.map-3.2.0-3-amd64 System.map-3.2.0-4-amd64 vmlinuz-3.2.0-3-amd64 vmlinuz-3.2.0-4-amd642. 启动初始化进程内核文件加载以后，就开始运行第一个程序 /sbin/init，它的作用是初始化系统环境。由于 init 是第一个运行的程序，它的进程编号（PID）就是 1。其他所有进程都从它衍生，都是它的子进程。3. 确定运行级别许多程序需要开机启动。它们在 Windows 叫做"服务"（service），在 Linux 就叫做"守护进程"（daemon）。init 进程的一大任务，就是去运行这些开机启动的程序。但是，不同的场合需要启动不同的程序，比如用作服务器时，需要启动 Apache，用作桌面就不需要。Linux 允许为不同的场合，分配不同的开机启动程序，这就叫做"运行级别"（runlevel）。也就是说，启动时根据"运行级别"，确定要运行哪些程序。Linux 预置七种运行级别（0-6）。一般来说，0 是关机，1 是单用户模式（也就是维护模式），6 是重启。运行级别 2-5，各个发行版不太一样，对于 Debian 来说，都是同样的多用户模式（也就是正常模式）。init 进程首先读取文件 /etc/inittab，它是运行级别的设置文件。如果你打开它，可以看到第一行是这样的：id:2:initdefault:initdefault 的值是 2，表明系统启动时的运行级别为 2。如果需要指定其他级别，可以手动修改这个值。那么，运行级别 2 有些什么程序呢？系统怎么知道每个级别应该加载哪些程序呢？答案是：每个运行级别在 /etc 目录下面，都有一个对应的子目录，指定要加载的程序。/etc/rc0.d /etc/rc1.d /etc/rc2.d /etc/rc3.d /etc/rc4.d /etc/rc5.d /etc/rc6.d上面目录名中的 rc，表示 run command（运行程序），最后的 d 表示 directory（目录）。下面让我们看看 /etc/rc2.d 目录中到底指定了哪些程序。$ ls /etc/rc2.d README S01motd S13rpcbind S14nfs-common S16binfmt-support S16rsyslog S16sudo S17apache2 S18acpid ...可以看到，除了第一个文件 README 以外，其他文件名都是"字母 S + 两位数字 + 程序名"的形式。字母 S 表示 Start，也就是启动的意思（启动脚本的运行参数为 start）；如果这个位置是字母 K，就代表 Kill（关闭），即如果从其他运行级别切换过来，需要关闭的程序（启动脚本的运行参数为 stop）。后面的两位数字表示处理顺序，数字越小越早处理，所以第一个启动的程序是 motd，然后是 rpcbind、nfs......数字相同时，则按照程序名的字母顺序启动，所以 rsyslog 会先于 sudo 启动。这个目录里的所有文件（除了 README），就是启动时要加载的程序。如果想增加或删除某些程序，不建议手动修改 /etc/rcN.d 目录，最好是用一些专门命令进行管理（参考 这里 和 这里）。4. 加载开机启动程序前面提到，七种预设的"运行级别"各自有一个目录，存放需要开机启动的程序。不难想到，如果多个"运行级别"需要启动同一个程序，那么这个程序的启动脚本，就会在每一个目录里都有一个拷贝。这样会造成管理上的困扰：如果要修改启动脚本，岂不是每个目录都要改一遍？Linux 的解决办法，就是七个 /etc/rcN.d 目录里列出的程序，都设为链接文件，指向另外一个目录 /etc/init.d，真正的启动脚本都统一放在这个目录中。init 进程逐一加载开机启动程序，其实就是运行这个目录里的启动脚本。下面就是链接文件真正的指向。$ ls -l /etc/rc2.d README S01motd -> ../init.d/motd S13rpcbind -> ../init.d/rpcbind S14nfs-common -> ../init.d/nfs-common S16binfmt-support -> ../init.d/binfmt-support S16rsyslog -> ../init.d/rsyslog S16sudo -> ../init.d/sudo S17apache2 -> ../init.d/apache2 S18acpid -> ../init.d/acpid ...这样做的另一个好处，就是如果你要手动关闭或重启某个进程，直接到目录 /etc/init.d 中寻找启动脚本即可。比如，我要重启 Apache 服务器，就运行下面的命令：$ sudo /etc/init.d/apache2 restart/etc/init.d 这个目录名最后一个字母 d，是 directory 的意思，表示这是一个目录，用来与程序 /etc/init 区分。5. 用户登录开机启动程序加载完毕以后，就要让用户登录了。一般来说，用户的登录方式有三种：命令行登录ssh 登录图形界面登录这三种情况，都有自己的方式对用户进行认证。命令行登录：init 进程调用 getty 程序（意为 get teletype），让用户输入用户名和密码。输入完成后，再调用 login 程序，核对密码（Debian 还会再多运行一个身份核对程序 /etc/pam.d/login）。如果密码正确，就从文件 /etc/passwd 读取该用户指定的 shell，然后启动这个 shell。ssh 登录：这时系统调用 sshd 程序（Debian 还会再运行 /etc/pam.d/ssh），取代 getty 和 login，然后启动 shell。图形界面登录：init 进程调用显示管理器，Gnome 图形界面对应的显示管理器为 gdm（GNOME Display Manager），然后用户输入用户名和密码。如果密码正确，就读取 /etc/gdm3/Xsession，启动用户的会话。6. 进入 Login Shell所谓 shell，简单说就是命令行界面，让用户可以直接与操作系统对话。用户登录时打开的 shell，就叫做 login shell。Debian 默认的 shell 是 Bash，它会读入一系列的配置文件。上一步的三种情况，在这一步的处理，也存在差异。命令行登录：首先读入 /etc/profile，这是对所有用户都有效的配置；然后依次寻找下面三个文件，这是针对当前用户的配置。~/.bash_profile ~/.bash_login ~/.profile需要注意的是，这三个文件只要有一个存在，就不再读入后面的文件了。比如，要是 ~/.bash_profile 存在，就不会再读入后面两个文件了。ssh 登录：与第一种情况完全相同。图形界面登录：只加载 /etc/profile 和 ~/.profile。也就是说，~/.bash_profile 不管有没有，都不会运行。7. 打开 Non-login Shell老实说，上一步完成以后，Linux 的启动过程就算结束了，用户已经可以看到命令行提示符或者图形界面了。但是，为了内容的完整，必须再介绍一下这一步。用户进入操作系统以后，常常会再手动开启一个 shell。这个 shell 就叫做 non-login shell，意思是它不同于登录时出现的那个 shell，不读取 /etc/profile 和 .profile 等配置文件。non-login shell 的重要性，不仅在于它是用户最常接触的那个 shell，还在于它会读入用户自己的 bash 配置文件 ~/.bashrc。大多数时候，我们对于 bash 的定制，都是写在这个文件里面的。你也许会问，要是不进入 non-login shell，岂不是 .bashrc 就不会运行了，因此 bash 也就不能完成定制了？事实上，Debian 已经考虑到这个问题了，请打开文件 ~/.profile，可以看到下面的代码：if [ -n "$BASH_VERSION" ]; then if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc" fi fi上面代码先判断变量 $BASH_VERSION 是否有值，然后判断主目录下是否存在 .bashrc 文件，如果存在就运行该文件。第三行开头的那个点，是 source 命令的简写形式，表示运行某个文件，写成 source ~/.bashrc 也是可以的。因此，只要运行 ~/.profile 文件，~/.bashrc 文件就会连带运行。但是上一节的第一种情况提到过，如果存在 ~/.bash_profile 文件，那么有可能不会运行 ~/.profile 文件。解决这个问题很简单，把下面代码写入 .bash_profile 就行了。if [ -f ~/.profile ]; then . ~/.profile fi这样一来，不管是哪种情况，.bashrc 都会执行，用户的设置可以放心地都写入这个文件了。Bash 的设置之所以如此繁琐，是由于历史原因造成的。早期的时候，计算机运行速度很慢，载入配置文件需要很长时间，Bash 的作者只好把配置文件分成了几个部分，阶段性载入。系统的通用设置放在 /etc/profile，用户个人的、需要被所有子进程继承的设置放在 .profile，不需要被继承的设置放在 .bashrc。顺便提一下，除了 Linux 以外，Mac OS X 使用的 shell 也是 Bash。但是，它只加载 .bash_profile，然后在 .bash_profile 里面调用 .bashrc。而且，不管是 ssh 登录，还是在图形界面里启动 shell 窗口，都是如此。参考链接Debian Wiki, Environment VariablesDebian Wiki, Dot FilesDebian Administration, An introduction to run-levelsDebian Admin, Debian and Ubuntu Linux Run LevelsLinux Information Project (LINFO), Runlevel DefinitionLinuxQuestions.org, What are run levels?Dalton Hubble, Bash Configurations Demystified说明：本文主要基于 SysV init 初始化系统撰写，适用于较早版本的 Debian 及传统 Linux 发行版。现代主流 Linux 发行版（如新版 Debian、Ubuntu、CentOS 7+ 等）已普遍采用 systemd 作为初始化系统，其启动流程、服务管理命令（如 systemctl）及目标（targets）概念与文中描述的 runlevel 机制有所不同，请以当前官方文档为准。
引言在 Linux Bash Shell 脚本编程中，正确理解单引号与双引号的区别至关重要。它们在处理变量扩展、特殊字符解析以及命令执行等方面遵循不同的规则。合理运用引号不仅能避免常见的语法错误，还能显著提升脚本的稳定性与可读性。一、单引号和双引号的基本区别（一）单引号（' '）严格的字面引用单引号内的所有字符均被视为字面量（Literal）。除了用于结束字符串的单引号本身外，其他特殊字符（如美元符号 $、反引号 ` 、反斜杠 \ 等）都会失去特殊含义。例如，定义变量 var='This is a $variable' 后执行 echo $var，Shell 不会解析 $variable，而是直接输出字符串原貌。代码示例：var='This is a $variable' echo $var输出结果：This is a $variable限制转义字符功能在单引号内部，反斜杠 \ 仅被视为普通字符，无法用于转义。因此，无法直接在单引号内嵌套单引号。若需在单引号字符串中包含单引号，必须采用特殊拼接方式。例如，直接写 echo 'I can't write this' 会导致语法错误，因为 can't 中的引号会提前终止字符串。正确做法是使用 '\'' 序列，其逻辑为：结束前一个单引号、添加一个转义的单引号、开启新的单引号。代码示例：echo 'I can'\''t write this'输出结果：I can't write this（二）双引号（" "）部分特殊字符解释双引号允许部分特殊字符保留其功能，主要包括变量扩展（$）、命令替换（ ` 或 $()）以及转义字符（\）。其余特殊字符则被视为字面量。变量会被其值替换，命令替换也会正常执行。代码示例：num=5 echo "The value of num is $num"输出结果：The value of num is 5防止单词分割双引号内的内容会被视为一个整体单元。即使字符串中包含空格或制表符，Shell 也不会对其进行单词分割（Word Splitting）。例如，variable="a variable with spaces"，使用 echo "This is $variable" 会将整个字符串作为一个参数传递；而若不加双引号 echo This is $variable，Shell 可能会将其分割为多个参数。代码示例：variable="a variable with spaces" echo "This is $variable" echo This is $variable输出结果：This is a variable with spaces This is a variable with spaces注意：虽然上述示例输出看起来相同，但在作为命令参数传递时（如传递给 rm 或 cp），是否加双引号会导致参数个数的本质区别。（三）使用场景对比不需要变量替换和命令替换时优先使用单引号。这能确保字符串的字面意义，避免 Shell 意外解析其中的特殊字符。适用于处理固定的文件路径、命令选项或正则表达式等场景。代码示例：echo 'The file is located at /home/user/documents/file.txt'输出结果：The file is located at /home/user/documents/file.txt需要变量替换或命令替换时必须使用双引号。在构建动态命令、输出包含变量值的信息等情况下，双引号能正确处理变量扩展和命令替换，同时防止单词分割。代码示例：name="John" echo "Hello, $name"输出结果：Hello, John二、命令替换：反引号与 $()（一）功能介绍反引号（ ` ）反引号内的语句会被当作命令优先执行，其标准输出结果将替换反引号部分，然后再执行整个命令。代码示例：echo `date`输出结果：Wed Aug 23 10:30:00 CST 2023$() 形式功能与反引号完全相同，但这是 POSIX 规范推荐的标准形式，且具有更好的嵌套可读性。代码示例：echo $(date)输出结果：Wed Aug 23 10:30:00 CST 2023（二）使用建议虽然反引号在大多数 Shell 环境中仍能正常工作，但为了遵循 POSIX 规范并提高脚本的兼容性与可维护性，建议始终使用 $() 形式进行命令替换。特别是在需要嵌套命令替换时，$() 的可读性远优于反引号。说明：本文内容基于标准 Bash Shell 行为，适用于大多数 Linux 发行版及 macOS 默认终端。关于命令替换，$() 为 POSIX 标准推荐写法，反引号虽兼容但已视为遗留语法。
一、简介rsync（remote sync）是一个常用的 Linux 应用程序，用于文件同步。它可以在本地计算机与远程计算机之间，或者两个本地目录之间同步文件（但不支持两台远程计算机之间的直接同步）。除了作为同步工具，它也可以当作文件复制工具，替代 cp 和 mv 命令。与其他文件传输工具（如 FTP 或 scp）不同，rsync 的最大特点是增量传输：它会检查发送方和接收方已有的文件，仅传输有变动的部分（默认规则是文件大小或修改时间有变动）。二、安装如果本机或者远程计算机没有安装 rsync，可以使用以下命令进行安装。注意，传输的双方都必须安装 rsync。# Debian/Ubuntu $ sudo apt-get install rsync # Red Hat/CentOS $ sudo yum install rsync # Arch Linux $ sudo pacman -S rsync三、基本用法3.1 -r 参数在本地使用 rsync 命令时，它可以作为 cp 和 mv 命令的替代方法，将源目录同步到目标目录。$ rsync -r source destination上面命令中，-r 表示递归（recursive），即包含子目录。注意，-r 是必须的，否则 rsync 无法处理目录。source 表示源目录，destination 表示目标目录。如果有多个文件或目录需要同步，可以写成下面这样：$ rsync -r source1 source2 destination上面命令中，source1 和 source2 都会被同步到 destination 目录中。3.2 -a 参数-a 参数可以替代 -r。除了递归同步以外，它还可以同步元信息（比如修改时间、权限、所有者等）。由于 rsync 默认使用文件大小和修改时间决定文件是否需要更新，所以 -a 比 -r 更常用。下面的用法才是标准的写法：$ rsync -a source destination目标目录 destination 如果不存在，rsync 会自动创建。执行上面的命令后，源目录 source 被完整地复制到了目标目录 destination 下面，即形成了 destination/source 的目录结构。如果只想同步源目录 source 里面的内容到目标目录 destination，则需要在源目录后面加上斜杠：$ rsync -a source/ destination上面命令执行后，source 目录里面的内容会被直接复制到 destination 目录里面，并不会在 destination 下面创建一个 source 子目录。3.3 -n 参数如果不确定 rsync 执行后会产生什么结果，可以先用 -n 或 --dry-run 参数模拟执行的结果。$ rsync -anv source/ destination上面命令中，-n 参数模拟命令执行的结果，并不真的执行命令。-v 参数则是将结果输出到终端，这样就可以看到哪些内容会被同步。3.4 --delete 参数默认情况下，rsync 只确保源目录的所有内容（明确排除的文件除外）都复制到目标目录。它不会使两个目录保持相同，并且不会删除目标目录中多余的文件。如果要使得目标目录成为源目录的镜像副本，则必须使用 --delete 参数。这将删除只存在于目标目录、不存在于源目录的文件。$ rsync -av --delete source/ destination上面命令中，--delete 参数会使得 destination 成为 source 的一个镜像。四、排除文件4.1 --exclude 参数有时，我们希望同步时排除某些文件或目录，这时可以用 --exclude 参数指定排除模式。$ rsync -av --exclude='*.txt' source/ destination # 或者 $ rsync -av --exclude '*.txt' source/ destination上面命令排除了所有 TXT 文件。注意，rsync 默认会同步以"点"开头的隐藏文件。如果要排除隐藏文件，可以这样写 --exclude=".*"。如果要排除某个目录里面的所有文件，但不希望排除目录本身，可以写成下面这样：$ rsync -av --exclude 'dir1/*' source/ destination多个排除模式，可以用多个 --exclude 参数：$ rsync -av --exclude 'file1.txt' --exclude 'dir1/*' source/ destination多个排除模式也可以利用 Bash 的大括号的扩展功能，只用一个 --exclude 参数：$ rsync -av --exclude={'file1.txt','dir1/*'} source/ destination如果排除模式很多，可以将它们写入一个文件，每个模式一行，然后用 --exclude-from 参数指定这个文件：$ rsync -av --exclude-from='exclude-file.txt' source/ destination4.2 --include 参数--include 参数用来指定必须同步的文件模式，往往与 --exclude 结合使用。$ rsync -av --include="*.txt" --exclude='*' source/ destination上面命令指定同步时，排除所有文件，但是会包括 TXT 文件。五、远程同步5.1 SSH 协议rsync 除了支持本地两个目录之间的同步，也支持远程同步。它可以将本地内容同步到远程服务器：$ rsync -av source/ username@remote_host:destination也可以将远程内容同步到本地：$ rsync -av username@remote_host:source/ destinationrsync 默认使用 SSH 进行远程登录和数据传输。由于早期 rsync 不使用 SSH 协议，需要用 -e 参数指定协议，后来才改为默认使用 SSH。所以，下面 -e ssh 通常可以省略：$ rsync -av -e ssh source/ user@remote_host:/destination但是，如果 ssh 命令有附加的参数（例如修改端口），则必须使用 -e 参数指定所要执行的 SSH 命令：$ rsync -av -e 'ssh -p 2234' source/ user@remote_host:/destination上面命令中，-e 参数指定 SSH 使用 2234 端口。5.2 rsync 协议除了使用 SSH，如果另一台服务器安装并运行了 rsync 守护程序，则也可以用 rsync:// 协议（默认端口 873）进行传输。具体写法是服务器与目标目录之间使用双冒号分隔 ::。$ rsync -av source/ 192.168.122.32::module/destination注意，上面地址中的 module 并不是实际路径名，而是 rsync 守护程序指定的一个资源名，由管理员分配。如果想知道 rsync 守护程序分配的所有 module 列表，可以执行下面命令：$ rsync rsync://192.168.122.32rsync 协议除了使用双冒号，也可以直接用 rsync:// 协议指定地址：$ rsync -av source/ rsync://192.168.122.32/module/destination六、增量备份rsync 的最大特点就是它可以完成增量备份，也就是默认只复制有变动的文件。除了源目录与目标目录直接比较，rsync 还支持使用基准目录，即将源目录与基准目录之间变动的部分，同步到目标目录。具体做法是：第一次同步是全量备份，所有文件在基准目录里面同步一份。以后每一次同步都是增量备份，只同步源目录与基准目录之间有变动的部分，将这部分保存在一个新的目标目录。这个新的目标目录之中，也是包含所有文件，但实际上，只有那些变动过的文件是存在于该目录，其他没有变动的文件都是指向基准目录文件的硬链接。--link-dest 参数用来指定同步时的基准目录。$ rsync -a --delete --link-dest /compare/path /source/path /target/path上面命令中，--link-dest 参数指定基准目录 /compare/path，然后源目录 /source/path 跟基准目录进行比较，找出变动的文件，将它们拷贝到目标目录 /target/path。那些没变动的文件则会生成硬链接。这个命令的第一次备份时是全量备份，后面就都是增量备份了。下面是一个脚本示例，用于备份用户的主目录：#!/bin/bash # A script to perform incremental backups using rsync set -o errexit set -o nounset set -o pipefail readonly SOURCE_DIR="${HOME}" readonly BACKUP_DIR="/mnt/data/backups" readonly DATETIME="$(date '+%Y-%m-%d_%H:%M:%S')" readonly BACKUP_PATH="${BACKUP_DIR}/${DATETIME}" readonly LATEST_LINK="${BACKUP_DIR}/latest" mkdir -p "${BACKUP_DIR}" rsync -av --delete \ "${SOURCE_DIR}/" \ --link-dest "${LATEST_LINK}" \ --exclude=".cache" \ "${BACKUP_PATH}" rm -rf "${LATEST_LINK}" ln -s "${BACKUP_PATH}" "${LATEST_LINK}"上面脚本中，每一次同步都会生成一个新目录 ${BACKUP_DIR}/${DATETIME}，并将软链接 ${BACKUP_DIR}/latest 指向这个目录。下一次备份时，就将 ${BACKUP_DIR}/latest 作为基准目录，生成新的备份目录。最后，再将软链接 ${BACKUP_DIR}/latest 指向新的备份目录。七、常用配置项以下是 rsync 常用参数的详细说明：-a, --archive：存档模式。保存所有的元数据（比如修改时间、权限、所有者等），并且软链接也会同步过去。--append：指定文件接着上次中断的地方，继续传输。--append-verify：跟 --append 类似，但会对传输完成后的文件进行一次校验。如果校验失败，将重新发送整个文件。-b, --backup：指定在删除或更新目标目录已经存在的文件时，将该文件更名后进行备份（默认行为是删除）。更名规则是添加由 --suffix 参数指定的文件后缀名，默认是 ~。--backup-dir：指定文件备份时存放的目录，例如 --backup-dir=/path/to/backups。--bwlimit：指定带宽限制，默认单位是 KB/s，例如 --bwlimit=100。-c, --checksum：改变 rsync 的校验方式。默认情况下，rsync 只检查文件的大小和最后修改日期；使用这个参数以后，则通过判断文件内容的校验和（checksum）决定是否重新传输。--delete：删除只存在于目标目录、不存在于源目录的文件，即保证目标目录是源目录的镜像。-e：指定使用 SSH 协议传输数据。--exclude：指定排除不进行同步的文件，例如 --exclude="*.iso"。--exclude-from：指定一个本地文件，里面是需要排除的文件模式，每个模式一行。--existing, --ignore-non-existing：表示不同步目标目录中不存在的文件和目录。-h：表示以人类可读的格式输出（human-readable）。--help：返回帮助信息。-i：输出源目录与目标目录之间文件差异的详细情况。--ignore-existing：表示只要该文件在目标目录中已经存在，就跳过去，不再同步这些文件。--include：指定同步时要包括的文件，一般与 --exclude 结合使用。--link-dest：指定增量备份的基准目录。-m：指定不同步空目录。--max-size：设置传输的最大文件的大小限制，例如不超过 200KB（--max-size='200k'）。--min-size：设置传输的最小文件的大小限制，例如不小于 10KB（--min-size=10k）。-n, --dry-run：模拟将要执行的操作，而并不真的执行。配合 -v 参数使用，可以看到哪些内容会被同步过去。-P：是 --progress 和 --partial 这两个参数的结合。--partial：允许恢复中断的传输。不使用该参数时，rsync 会删除传输到一半被打断的文件；使用该参数后，传输到一半的文件也会同步到目标目录，下次同步时再恢复中断的传输。一般需要与 --append 或 --append-verify 配合使用。--partial-dir：指定将传输到一半的文件保存到一个临时目录，例如 --partial-dir=.rsync-partial。--progress：显示传输进展。-r：表示递归，即包含子目录。--remove-source-files：表示传输成功后，删除发送方的文件。--size-only：表示只同步大小有变化的文件，不考虑文件修改时间的差异。--suffix：指定文件名备份时，对文件名添加的后缀，默认是 ~。-u, --update：表示同步时跳过目标目录中修改时间更新的文件，即不同步这些有更新的时间戳的文件。-v：输出细节。-vv 表示输出更详细的信息，-vvv 表示输出最详细的信息。--version：返回 rsync 的版本。-z：指定同步时压缩数据。八、参考链接How To Use Rsync to Sync Local and Remote Directories on a VPS, Justin EllingwoodMirror Your Web Site With rsync, Falko TimmeExamples on how to use Rsync, Egidio DocileHow to create incremental backups using rsync on Linux, Egidio Docile
编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。上一篇文章介绍了 Systemd 的主要命令，本文将介绍如何使用它完成一些基本的任务。一、开机启动对于支持 Systemd 的软件，安装时通常会自动在 /usr/lib/systemd/system 目录添加一个配置文件。若想让该软件开机启动，可执行以下命令（以 httpd.service 为例）：$ sudo systemctl enable httpd上面的命令相当于在 /etc/systemd/system 目录添加一个符号链接，指向 /usr/lib/systemd/system 里面的 httpd.service 文件。这是因为开机时，Systemd 只执行 /etc/systemd/system 目录里面的配置文件。这也意味着，如果把修改后的配置文件放在该目录，就可以达到覆盖原始配置的效果。二、启动服务设置开机启动以后，软件并不会立即启动，必须等到下一次开机。如果想现在就运行该软件，需要执行 systemctl start 命令。$ sudo systemctl start httpd执行上面的命令以后，有可能启动失败，因此要用 systemctl status 命令查看一下该服务的状态。$ sudo systemctl status httpd httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled) Active: active (running) since 金 2014-12-05 12:18:22 JST; 7min ago Main PID: 4349 (httpd) Status: "Total requests: 1; Current requests/sec: 0; Current traffic: 0 B/sec" CGroup: /system.slice/httpd.service ├─4349 /usr/sbin/httpd -DFOREGROUND ├─4350 /usr/sbin/httpd -DFOREGROUND ├─4351 /usr/sbin/httpd -DFOREGROUND ├─4352 /usr/sbin/httpd -DFOREGROUND ├─4353 /usr/sbin/httpd -DFOREGROUND └─4354 /usr/sbin/httpd -DFOREGROUND 12 月 05 12:18:22 localhost.localdomain systemd[1]: Starting The Apache HTTP Server... 12 月 05 12:18:22 localhost.localdomain systemd[1]: Started The Apache HTTP Server. 12 月 05 12:22:40 localhost.localdomain systemd[1]: Started The Apache HTTP Server.上面的输出结果含义如下：Loaded 行：配置文件的位置，是否设为开机启动。Active 行：表示正在运行。Main PID 行：主进程 ID。Status 行：由应用本身（这里是 httpd）提供的软件当前状态。CGroup 块：应用的所有子进程。日志块：应用的日志。三、停止服务终止正在运行的服务，需要执行 systemctl stop 命令。$ sudo systemctl stop httpd.service有时候，该命令可能没有响应，服务停不下来。这时候就不得不强制终止进程了，向正在运行的进程发出 kill 信号。$ sudo systemctl kill httpd.service此外，重启服务要执行 systemctl restart 命令。$ sudo systemctl restart httpd.service四、读懂配置文件一个服务怎么启动，完全由它的配置文件决定。下面就来看，配置文件有些什么内容。前面说过，配置文件主要放在 /usr/lib/systemd/system 目录，也可能在 /etc/systemd/system 目录。找到配置文件以后，使用文本编辑器打开即可。systemctl cat 命令可以用来查看配置文件，下面以 sshd.service 文件为例，它的作用是启动一个 SSH 服务器，供其他用户以 SSH 方式登录。$ systemctl cat sshd.service [Unit] Description=OpenSSH server daemon Documentation=man:sshd(8) man:sshd_config(5) After=network.target sshd-keygen.service Wants=sshd-keygen.service [Service] EnvironmentFile=/etc/sysconfig/sshd ExecStart=/usr/sbin/sshd -D $OPTIONS ExecReload=/bin/kill -HUP $MAINPID Type=simple KillMode=process Restart=on-failure RestartSec=42s [Install] WantedBy=multi-user.target可以看到，配置文件分成几个区块，每个区块包含若干条键值对。下面依次解释每个区块的内容。五、[Unit] 区块：启动顺序与依赖关系Unit 区块的 Description 字段给出当前服务的简单描述，Documentation 字段给出文档位置。接下来的设置是启动顺序和依赖关系，这个比较重要。After 字段：表示如果 network.target 或 sshd-keygen.service 需要启动，那么 sshd.service 应该在它们之后启动。相应地，还有一个 Before 字段，定义 sshd.service 应该在哪些服务之前启动。注意，After 和 Before 字段只涉及启动顺序，不涉及依赖关系。举例来说，某 Web 应用需要 postgresql 数据库储存数据。在配置文件中，它只定义要在 postgresql 之后启动，而没有定义依赖 postgresql。上线后，由于某种原因，postgresql 需要重新启动，在停止服务期间，该 Web 应用就会无法建立数据库连接。设置依赖关系，需要使用 Wants 字段和 Requires 字段。Wants 字段：表示 sshd.service 与 sshd-keygen.service 之间存在“弱依赖”关系，即如果 sshd-keygen.service 启动失败或停止运行，不影响 sshd.service 继续执行。Requires 字段则表示“强依赖”关系，即如果该服务启动失败或异常退出，那么 sshd.service 也必须退出。注意，Wants 字段与 Requires 字段只涉及依赖关系，与启动顺序无关，默认情况下是同时启动的。六、[Service] 区块：启动行为Service 区块定义如何启动当前服务。6.1 启动命令许多软件都有自己的环境参数文件，该文件可以用 EnvironmentFile 字段读取。EnvironmentFile 字段：指定当前服务的环境参数文件。该文件内部的 key=value 键值对，可以用 $key 的形式，在当前配置文件中获取。上面的例子中，sshd 的环境参数文件是 /etc/sysconfig/sshd。配置文件里面最重要的字段是 ExecStart。ExecStart 字段：定义启动进程时执行的命令。上面的例子中，启动 sshd 执行的命令是 /usr/sbin/sshd -D $OPTIONS，其中的变量 $OPTIONS 就来自 EnvironmentFile 字段指定的环境参数文件。与之作用相似的，还有如下这些字段：ExecReload 字段：重启服务时执行的命令。ExecStop 字段：停止服务时执行的命令。ExecStartPre 字段：启动服务之前执行的命令。ExecStartPost 字段：启动服务之后执行的命令。ExecStopPost 字段：停止服务之后执行的命令。请看下面的例子：[Service] ExecStart=/bin/echo execstart1 ExecStart= ExecStart=/bin/echo execstart2 ExecStartPost=/bin/echo post1 ExecStartPost=/bin/echo post2上面这个配置文件，第二行 ExecStart 设为空值，等于取消了第一行的设置，运行结果如下：execstart2 post1 post2所有的启动设置之前，都可以加上一个连字符（-），表示“抑制错误”，即发生错误的时候，不影响其他命令的执行。比如，EnvironmentFile=-/etc/sysconfig/sshd（注意等号后面的那个连字符），就表示即使 /etc/sysconfig/sshd 文件不存在，也不会抛出错误。6.2 启动类型Type 字段定义启动类型。它可以设置的值如下：simple（默认值）：ExecStart 字段启动的进程为主进程。forking：ExecStart 字段将以 fork() 方式启动，此时父进程将会退出，子进程将成为主进程。oneshot：类似于 simple，但只执行一次，Systemd 会等它执行完，才启动其他服务。dbus：类似于 simple，但会等待 D-Bus 信号后启动。notify：类似于 simple，启动结束后会发出通知信号，然后 Systemd 再启动其他服务。idle：类似于 simple，但是要等到其他任务都执行完，才会启动该服务。一种使用场合是为让该服务的输出，不与其他服务的输出相混合。下面是一个 oneshot 的例子，笔记本电脑启动时，要把触摸板关掉，配置文件可以这样写：[Unit] Description=Switch-off Touchpad [Service] Type=oneshot ExecStart=/usr/bin/touchpad-off [Install] WantedBy=multi-user.target上面的配置文件，启动类型设为 oneshot，就表明这个服务只要运行一次就够了，不需要长期运行。如果关闭以后，将来某个时候还想打开，配置文件修改如下：[Unit] Description=Switch-off Touchpad [Service] Type=oneshot ExecStart=/usr/bin/touchpad-off start ExecStop=/usr/bin/touchpad-off stop RemainAfterExit=yes [Install] WantedBy=multi-user.target上面配置文件中，RemainAfterExit 字段设为 yes，表示进程退出以后，服务仍然保持执行。这样的话，一旦使用 systemctl stop 命令停止服务，ExecStop 指定的命令就会执行，从而重新开启触摸板。6.3 重启行为Service 区块有一些字段，定义了重启行为。KillMode 字段：定义 Systemd 如何停止 sshd 服务。上面这个例子中，将 KillMode 设为 process，表示只停止主进程，不停止任何 sshd 子进程，即子进程打开的 SSH session 仍然保持连接。这个设置不太常见，但对 sshd 很重要，否则你停止服务的时候，会连自己打开的 SSH session 一起杀掉。KillMode 字段可以设置的值如下：control-group（默认值）：当前控制组里面的所有子进程，都会被杀掉。process：只杀主进程。mixed：主进程将收到 SIGTERM 信号，子进程收到 SIGKILL 信号。none：没有进程会被杀掉，只是执行服务的 stop 命令。接下来是 Restart 字段。Restart 字段：定义了 sshd 退出后，Systemd 的重启方式。上面的例子中，Restart 设为 on-failure，表示任何意外的失败，就将重启 sshd。如果 sshd 正常停止（比如执行 systemctl stop 命令），它就不会重启。Restart 字段可以设置的值如下：no（默认值）：退出后不会重启。on-success：只有正常退出时（退出状态码为 0），才会重启。on-failure：非正常退出时（退出状态码非 0），包括被信号终止和超时，才会重启。on-abnormal：只有被信号终止和超时，才会重启。on-abort：只有在收到没有捕捉到的信号终止时，才会重启。on-watchdog：超时退出，才会重启。always：不管是什么退出原因，总是重启。对于守护进程，推荐设为 on-failure。对于那些允许发生错误退出的服务，可以设为 on-abnormal。最后是 RestartSec 字段。RestartSec 字段：表示 Systemd 重启服务之前，需要等待的秒数。上面的例子设为等待 42 秒。七、[Install] 区块Install 区块，定义如何安装这个配置文件，即怎样做到开机启动。WantedBy 字段：表示该服务所在的 Target。Target 的含义是服务组，表示一组服务。WantedBy=multi-user.target 指的是，sshd 所在的 Target 是 multi-user.target。这个设置非常重要，因为执行 systemctl enable sshd.service 命令时，sshd.service 的一个符号链接，就会放在 /etc/systemd/system 目录下面的 multi-user.target.wants 子目录之中。Systemd 有默认的启动 Target。$ systemctl get-default multi-user.target上面的结果表示，默认的启动 Target 是 multi-user.target。在这个组里的所有服务，都将开机启动。这就是为什么 systemctl enable 命令能设置开机启动的原因。使用 Target 的时候，systemctl list-dependencies 命令和 systemctl isolate 命令也很有用。# 查看 multi-user.target 包含的所有服务 $ systemctl list-dependencies multi-user.target # 切换到另一个 target # shutdown.target 就是关机状态 $ sudo systemctl isolate shutdown.target一般来说，常用的 Target 有两个：一个是 multi-user.target，表示多用户命令行状态；另一个是 graphical.target，表示图形用户状态，它依赖于 multi-user.target。官方文档有一张非常清晰的 Target 依赖关系图。八、Target 的配置文件Target 也有自己的配置文件。$ systemctl cat multi-user.target [Unit] Description=Multi-User System Documentation=man:systemd.special(7) Requires=basic.target Conflicts=rescue.service rescue.target After=basic.target rescue.service rescue.target AllowIsolate=yes注意，Target 配置文件里面没有启动命令。上面输出结果中，主要字段含义如下：Requires 字段：要求 basic.target 一起运行。Conflicts 字段：冲突字段。如果 rescue.service 或 rescue.target 正在运行，multi-user.target 就不能运行，反之亦然。After：表示 multi-user.target 在 basic.target、rescue.service、rescue.target 之后启动，如果它们有启动的话。AllowIsolate：允许使用 systemctl isolate 命令切换到 multi-user.target。九、修改配置文件后重启修改配置文件以后，需要重新加载配置文件，然后重新启动相关服务。# 重新加载配置文件 $ sudo systemctl daemon-reload # 重启相关服务 $ sudo systemctl restart foobar