#### Shell 一行命令  [[目录]](#anger-table-of-contents) ##### 目录 * [terminal](#tool-terminal) - 终端 * [busybox](#tool-busybox) * [mount](#tool-mount) - 挂载 * [fuser](#tool-fuser) * [lsof](#tool-lsof) * [ps](#tool-ps) * [top](#tool-top) * [vmstat](#tool-vmstat) * [iostat](#tool-iostat) * [strace](#tool-strace) * [kill](#tool-kill) * [find](#tool-find) * [diff](#tool-diff) * [vimdiff](#tool-vimdiff) * [tail](#tool-tail) * [cpulimit](#tool-cpulimit) * [pwdx](#tool-pwdx) * [tr](#tool-tr) * [chmod](#tool-chmod) * [who](#tool-who) * [last](#tool-last) * [screen](#tool-screen) * [script](#tool-script) * [du](#tool-du) * [inotifywait](#tool-inotifywait) * [openssl](#tool-openssl) * [secure-delete](#tool-secure-delete) - 安全删除 * [dd](#tool-dd) * [gpg](#tool-gpg) * [system-other](#tool-system-other) - 系统其他 * [curl](#tool-curl) * [httpie](#tool-httpie) * [ssh](#tool-ssh) * [linux-dev](#tool-linux-dev) * [tcpdump](#tool-tcpdump) * [tcpick](#tool-tcpick) * [ngrep](#tool-ngrep) * [hping3](#tool-hping3) * [nmap](#tool-nmap) * [netcat](#tool-netcat) * [socat](#tool-socat) * [p0f](#tool-p0f) * [gnutls-cli](#tool-gnutls-cli) * [netstat](#tool-netstat) * [rsync](#tool-rsync) * [host](#tool-host) * [dig](#tool-dig) * [certbot](#tool-certbot) * [network-other](#tool-network-other) - 网络其他 * [git](#tool-git) * [awk](#tool-awk) * [sed](#tool-sed) * [grep](#tool-grep) * [perl](#tool-perl) ##### 工具: [terminal](https://en.wikipedia.org/wiki/Linux_console) - 终端 ###### 不退出重新加载 Shell ```bash exec $SHELL -l ``` ###### 关闭 Shell 但保持所有子进程运行 ```bash disown -a && exit ``` ###### 退出不保存 Shell 历史 ```bash kill -9 $$ unset HISTFILE && exit ``` ###### 执行分支条件判断 ```bash true && echo success false || echo failed ``` ###### 将 stdout 和 stderr 管道到不同的命令 ```bash some_command > >(/bin/cmd_for_stdout) 2> >(/bin/cmd_for_stderr) ``` ###### 将 stdout 和 stderr 分别重定向到不同文件,并同时打印到屏幕 ```bash (some_command 2>&1 1>&3 | tee errorlog ) 3>&1 1>&2 | tee stdoutlog ``` ###### 列出最常用的命令 ```bash history | \ awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}' | \ grep -v "./" | \ column -c3 -s " " -t | \ sort -nr | nl | head -n 20 ``` ###### 净化 Bash 历史 ```bash function sterile() { history | awk '$2 != "history" { $1=""; print $0 }' | egrep -vi "\ curl\b+.*(-E|--cert)\b+.*\b*|\ curl\b+.*--pass\b+.*\b*|\ curl\b+.*(-U|--proxy-user).*:.*\b*|\ curl\b+.*(-u|--user).*:.*\b* .*(-H|--header).*(token|auth.*)\b+.*|\ wget\b+.*--.*password\b+.*\b*|\ http.?://.+:.+@.*\ " > $HOME/histbuff; history -r $HOME/histbuff; } export PROMPT_COMMAND="sterile" ``` > 另见: [A naive utility to censor credentials in command history](https://github.com/lbonanomi/go/blob/master/revisionist.go)。 ###### 快速备份文件 ```bash cp filename{,.orig} ``` ###### 清空文件(截断为 0 大小) ```bash >filename ``` ###### 删除文件夹中不匹配特定文件扩展名的所有文件 ```bash rm !(*.foo|*.bar|*.baz) ``` ###### 将多行字符串传入文件 ```bash # cat >filename ... - 覆写文件 # cat >>filename ... - 追加到文件 cat > filename << __EOF__ data data data __EOF__ ``` ###### 使用 vim 编辑远程主机上的文件 ```bash vim scp://user@host//etc/fstab ``` ###### 创建目录并同时进入 ```bash mkd() { mkdir -p "$@" && cd "$@"; } ``` ###### 将大写文件名转换为小写 ```bash rename 'y/A-Z/a-z/' * ``` ###### 在终端上打印一行字符 ```bash printf "%`tput cols`s" | tr ' ' '#' ``` ###### 显示 Shell 历史但不显示行号 ```bash history | cut -c 8- fc -l -n 1 | sed 's/^\s*//' ``` ###### 退出会话后运行命令 ```bash cat > /etc/profile << __EOF__ _after_logout() { username=$(whoami) for _pid in $(ps afx | grep sshd | grep "$username" | awk '{print $1}') ; do kill -9 $_pid done } trap _after_logout EXIT __EOF__ ``` ###### 生成数字序列 ```bash for ((i=1; i<=10; i+=2)) ; do echo $i ; done # 替代方案: seq 1 2 10 for ((i=5; i<=10; ++i)) ; do printf '%02d\n' $i ; done # 替代方案: seq -w 5 10 for i in {1..10} ; do echo $i ; done ``` ###### 简单的 Bash 文件监视 ```bash unset MAIL; export MAILCHECK=1; export MAILPATH='$FILE_TO_WATCH?$MESSAGE' ``` --- ##### 工具: [busybox](https://www.busybox.net/) ###### 静态 HTTP Web 服务器 ```bash busybox httpd -p $PORT -h $HOME [-c httpd.conf] ``` ___ ##### 工具: [mount](https://en.wikipedia.org/wiki/Mount_(Unix)) - 挂载 ###### 挂载临时内存分区 ```bash mount -t tmpfs tmpfs /mnt -o size=64M ``` * `-t` - 文件系统类型 * `-o` - 挂载选项 ###### 将文件系统重新挂载为读写 ```bash mount -o remount,rw / ``` ___ ##### 工具: [fuser](https://en.wikipedia.org/wiki/Fuser_(Unix)) ###### 显示哪些进程使用指定文件/目录 ```bash fuser /var/log/daemon.log fuser -v /home/supervisor ``` ###### 杀死锁定文件的进程 ```bash fuser -ki filename ``` * `-i` - 交互式选项 ###### 使用特定信号杀死锁定文件的进程 ```bash fuser -k -HUP filename ``` * `--list-signals` - 列出可用信号名称 ###### 显示特定端口上监听的 PID ```bash fuser -v 53/udp ``` ###### 显示所有使用指定文件系统或块设备的进程 ```bash fuser -mv /var/www ``` ___ ##### 工具: [lsof](https://en.wikipedia.org/wiki/Lsof) ###### 显示当前使用互联网连接的进程 ```bash lsof -P -i -n ``` ###### 显示使用特定端口号的进程 ```bash lsof -i tcp:443 ``` ###### 列出所有监听端口及关联进程的 PID ```bash lsof -Pan -i tcp -i udp ``` ###### 列出所有开放端口及其所属可执行文件 ```bash lsof -i -P | grep -i "listen" ``` ###### 显示所有开放端口 ```bash lsof -Pnl -i ``` ###### 显示开放端口 (LISTEN) ```bash lsof -Pni4 | grep LISTEN | column -t ``` ###### 列出特定命令打开的所有文件 ```bash lsof -c "process" ``` ###### 查看每个目录的用户活动 ```bash lsof -u username -a +D /etc ``` ###### 显示 10 个最大的打开文件 ```bash lsof / | \ awk '{ if($7 > 1048576) print $7/1048576 "MB" " " $9 " " $1 }' | \ sort -n -u | tail | column -t ``` ###### 显示进程的当前工作目录 ```bash lsof -p | grep cwd ``` ___ ##### 工具: [ps](https://en.wikipedia.org/wiki/Ps_(Unix)) ###### 显示可四向滚动的进程树及完整详情 ```bash ps awwfux | less -S ``` ###### 每个用户的进程数统计 ```bash ps hax -o user | sort | uniq -c | sort -r ``` ###### 按名称显示所有进程及主头部 ```bash ps -lfC nginx ``` ___ ##### 工具: [find](https://en.wikipedia.org/wiki/Find_(Unix)) - 查找 ###### 查找系统中过去 60 分钟内修改过的文件 ```bash find / -mmin 60 -type f ``` ###### 查找所有大于 20M 的文件 ```bash find / -type f -size +20M ``` ###### 查找重复文件(基于 MD5 哈希) ```bash find -type f -exec md5sum '{}' ';' | sort | uniq --all-repeated=separate -w 33 ``` ###### 仅更改文件权限 ```bash cd /var/www/site && find . -type f -exec chmod 766 {} \; cd /var/www/site && find . -type f -exec chmod 664 {} + ``` ###### 仅更改目录权限 ```bash cd /var/www/site && find . -type d -exec chmod g+x {} \; cd /var/www/site && find . -type d -exec chmod g+rwx {} + ``` ###### 查找特定用户/组的文件和目录 ```bash # 用户: find . -user -print find /etc -type f -user -name "*.conf" # 组: find /opt -group find /etc -type f -group -iname "*.conf" ``` ###### 查找不属于特定用户/组的文件和目录 ```bash # 用户: find . \! -user -print # 组: find . \! -group ``` ###### 查找仅具有特定权限的文件/目录 ```bash # 用户 find . -user -perm -u+rw # -rw-r--r-- find /home -user $(whoami) -perm 777 # -rwxrwxrwx # 组: find /home -type d -group -perm 755 # -rwxr-xr-x ``` ###### 删除 60 天前的文件 ```bash find . -type f -mtime +60 -delete ``` ###### 递归删除目录中所有空子目录 ```bash find . -depth -type d -empty -exec rmdir {} \; ``` ###### 查找文件的所有硬链接 ```bash find -xdev -samefile filename ``` ###### 递归查找最近修改的文件 ```bash find . -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head ``` ###### 使用 sed 递归查找/替换字符串 ```bash find . -not -path '*/\.git*' -type f -print0 | xargs -0 sed -i 's/foo/bar/g' ``` ###### 递归查找/替换目录和文件名中的字符串 ```bash find . -depth -name '*test*' -execdir bash -c 'mv -v "$1" "${1//foo/bar}"' _ {} \; ``` ###### 递归查找 suid 可执行文件 ```bash find / \( -perm -4000 -o -perm -2000 \) -type f -exec ls -la {} \; ``` ___ ##### 工具: [top](https://en.wikipedia.org/wiki/Top_(software)) ###### 使用 top 仅监控包含特定字符串的进程 ```bash top -p $(pgrep -d , ) ``` * `` - 包含特定字符串的进程(如 nginx, worker) ___ ##### 工具: [vmstat](https://en.wikipedia.org/wiki/Vmstat) ###### 显示当前系统利用率(字段以千字节为单位) ```bash vmstat 2 20 -t -w ``` * `2` - 定义时间间隔的次数(延迟) * `20` - 每次执行命令的次数(计数) * `-t` - 显示时间戳 * `-w` - 宽输出 * `-S M` - 以兆字节为单位输出字段 ###### 每 5 秒刷新显示当前系统利用率 ```bash vmstat 5 -w ``` ###### 显示磁盘操作摘要报告 ```bash vmstat -D ``` ###### 显示事件计数器和内存统计报告 ```bash vmstat -s ``` ###### 显示关于 slab 层缓存中内核对象的报告 ```bash vmstat -m ``` ##### 工具: [iostat](https://en.wikipedia.org/wiki/Iostat) ###### 显示 CPU 使用信息和所有分区的 I/O 统计 ```bash iostat 2 10 -t -m ``` * `2` - 时间间隔(延迟) * `10` - 执行次数(计数) * `-t` - 显示时间戳 * `-m` - 以兆字节为单位(`-k` 以千字节为单位,默认) ###### 仅显示 CPU 利用率信息 ```bash iostat 2 10 -t -m -c ``` ###### 仅显示磁盘利用率信息 ```bash iostat 2 10 -t -m -d ``` ###### 仅显示 LVM 利用率信息 ```bash iostat -N ``` ___ ##### 工具: [strace](https://en.wikipedia.org/wiki/Strace) ###### 带子进程追踪 ```bash # 1) strace -f -p $(pidof glusterfsd) # 2) strace -f $(pidof php-fpm | sed 's/\([0-9]*\)/\-p \1/g') ``` ###### 限制追踪进程 30 秒 ```bash timeout 30 strace $(< /var/run/zabbix/zabbix_agentd.pid) ``` ###### 追踪进程并将输出重定向到文件 ```bash ps auxw | grep '[a]pache' | awk '{print " -p " $2}' | \ xargs strace -o /tmp/strace-apache-proc.out ``` ###### 追踪并打印每个系统调用的耗时,限制打印字符串长度 ```bash ps auxw | grep '[i]init_policy' | awk '{print " -p " $2}' | \ xargs strace -f -e trace=network -T -s 10000 ``` ###### 追踪网络端口的打开请求 ```bash strace -f -e trace=bind nc -l 80 ``` ###### 追踪网络端口的打开请求(显示 TCP/UDP) ```bash strace -f -e trace=network nc -lu 80 ``` ___ ##### 工具: [kill](https://en.wikipedia.org/wiki/Kill_(command)) ###### 杀死在端口上运行的进程 ```bash kill -9 $(lsof -i : | awk '{l=$2} END {print l}') ``` ___ ##### 工具: [diff](https://en.wikipedia.org/wiki/Diff) ###### 比较两个目录树 ```bash diff <(cd directory1 && find | sort) <(cd directory2 && find | sort) ``` ###### 比较两个命令的输出 ```bash diff <(cat /etc/passwd) <(cut -f2 /etc/passwd) ``` ___ ##### 工具: [vimdiff](http://vimdoc.sourceforge.net/htmldoc/diff.html) ###### 基于字符和单词高亮精确差异 ```bash vimdiff file1 file2 ``` ###### 比较两个 JSON 文件 ```bash vimdiff <(jq -S . A.json) <(jq -S . B.json) ``` ###### 比较十六进制转储 ```bash d(){ vimdiff <(f $1) <(f $2);};f(){ hexdump -C $1 | cut -d' ' -f3- | tr -s ' ';}; d ~/bin1 ~/bin2 ``` ###### diffchar 将 [diffchar](https://raw.githubusercontent.com/vim-scripts/diffchar.vim/master/plugin/diffchar.vim) 保存到 `~/.vim/plugins` 按 `F7` 在差异模式之间切换 有用的 `vimdiff` 命令: * `qa` 退出所有窗口 * `:vertical resize 70` 调整窗口大小 * 设置窗口宽度 `Ctrl+W [N columns]+(Shift+)<\>` ___ ##### 工具: [tail](https://en.wikipedia.org/wiki/Tail_(Unix)) ###### 为 tail -f 添加时间戳注释 ```bash tail -f file | while read ; do echo "$(date +%T.%N) $REPLY" ; done ``` ###### 分析 Apache 访问日志中最常见的 IP 地址 ```bash tail -10000 access_log | awk '{print $1}' | sort | uniq -c | sort -n | tail ``` ###### 分析 Web 服务器日志并仅显示 5xx HTTP 状态码 ```bash tail -n 100 -f /path/to/logfile | grep "HTTP/[1-2].[0-1]\" [5]" ``` ___ ##### 工具: [tar](https://en.wikipedia.org/wiki/Tar_(computing)) ###### 系统备份并排除特定目录 ```bash cd / tar -czvpf /mnt/system$(date +%d%m%Y%s).tgz --directory=/ \ --exclude=proc/* --exclude=sys/* --exclude=dev/* --exclude=mnt/* . ``` ###### 系统备份并排除特定目录 (使用 pigz 并行压缩) ```bash cd / tar cvpf /backup/snapshot-$(date +%d%m%Y%s).tgz --directory=/ \ --exclude=proc/* --exclude=sys/* --exclude=dev/* \ --exclude=mnt/* --exclude=tmp/* --use-compress-program=pigz . ``` ___ ##### 工具: [dump](https://en.wikipedia.org/wiki/Dump_(program)) ###### 系统备份到文件 ```bash dump -y -u -f /backup/system$(date +%d%m%Y%s).lzo / ``` ###### 从 lzo 文件还原系统 ```bash cd / restore -rf /backup/system$(date +%d%m%Y%s).lzo ``` ___ ##### 工具: [cpulimit](http://cpulimit.sourceforge.net/) ###### 限制进程的 CPU 使用率 ```bash cpulimit -p pid -l 50 ``` ___ ##### 工具: [pwdx](https://www.cyberciti.biz/faq/unix-linux-pwdx-command-examples-usage-syntax/) ###### 显示进程的当前工作目录 ```bash pwdx ``` ___ ##### 工具: [taskset](https://www.cyberciti.biz/faq/taskset-cpu-affinity-command/) ###### 仅在单个 CPU 核心上启动命令 ```bash taskset -c 0 ``` ___ ##### 工具: [tr](https://en.wikipedia.org/wiki/Tr_(Unix)) ###### 显示 PATH 中的目录,每行一个 ```bash tr : '\n' <<<$PATH ``` ___ ##### 工具: [chmod](https://en.wikipedia.org/wiki/Chmod) ###### 从当前目录中的所有文件移除可执行位 ```bash chmod -R -x+X * ``` ###### 恢复 /bin/chmod 的权限 ```bash # 1: cp /bin/ls chmod.01 cp /bin/chmod chmod.01 ./chmod.01 700 file # 2: /bin/busybox chmod 0700 /bin/chmod # 3: setfacl --set u::rwx,g::---,o::--- /bin/chmod ``` ___ ##### 工具: [who](https://en.wikipedia.org/wiki/Who_(Unix)) ###### 查找上次重启时间 ```bash who -b ``` ###### 检测用户是否通过 sudo su 进入当前 Shell ```bash [[ $(who -m | awk '{ print $1 }') == $(whoami) ]] || echo "You are su-ed to $(whoami)" ``` ___ ##### 工具: [last](https://www.howtoforge.com/linux-last-command/) ###### 上次重启是意外崩溃吗? ```bash (last -x -f $(ls -1t /var/log/wtmp* | head -2 | tail -1); last -x -f /var/log/wtmp) | \ grep -A1 reboot | head -2 | grep -q shutdown && echo "Expected reboot" || echo "Panic reboot" ``` ___ ##### 工具: [screen](https://en.wikipedia.org/wiki/GNU_Screen) ###### 在分离模式下启动 screen ```bash screen -d -m ``` ###### 附加到现有的 screen 会话 ```bash screen -r -d ``` ___ ##### 工具: [script](https://en.wikipedia.org/wiki/Script_(Unix)) ###### 录制和回放终端会话 ```bash ### 录制会话 # 1) script -t 2>~/session.time -a ~/session.log # 2) script --timing=session.time session.log ### 回放会话 scriptreplay --timing=session.time session.log ``` ___ ##### 工具: [du](https://en.wikipedia.org/wiki/GNU_Screen) ###### 显示 20 个最大的目录(以 K/M/G 标记) ```bash du | \ sort -r -n | \ awk '{split("K M G",v); s=1; while($1>1024){$1/=1024; s++} print int($1)" "v[s]"\t"$2}' | \ head -n 20 ``` ___ ##### 工具: [inotifywait](https://en.wikipedia.org/wiki/GNU_Screen) ###### 每当目录中的文件被修改时触发操作 ```bash while true ; do inotifywait -r -e MODIFY dir/ && ls dir/ ; done; ``` ___ ##### 工具: [openssl](https://www.openssl.org/) ###### 测试到远程主机的连接 ```bash echo | openssl s_client -connect google.com:443 -showcerts ``` ###### 测试到远程主机的连接(调试模式) ```bash echo | openssl s_client -connect google.com:443 -showcerts -tlsextdebug -status ``` ###### 测试到远程主机的连接(支持 SNI) ```bash echo | openssl s_client -showcerts -servername google.com -connect google.com:443 ``` ###### 使用特定 SSL 版本测试到远程主机的连接 ```bash openssl s_client -tls1_2 -connect google.com:443 ``` ###### 使用特定 SSL 密码测试到远程主机的连接 ```bash openssl s_client -cipher 'AES128-SHA' -connect google.com:443 ``` ###### 验证 0-RTT ```bash _host="example.com" cat > req.in << __EOF__ HEAD / HTTP/1.1 Host: $_host Connection: close __EOF__ openssl s_client -connect ${_host}:443 -tls1_3 -sess_out session.pem -ign_eof < req.in openssl s_client -connect ${_host}:443 -tls1_3 -sess_in session.pem -early_data req.in ``` ###### 生成无密码保护的私钥 ```bash # _len: 2048, 4096 ( _fd="private.key" ; _len="2048" ; \ openssl genrsa -out ${_fd} ${_len} ) ``` ###### 生成带密码保护的私钥 ```bash # _ciph: aes128, aes256 # _len: 2048, 4096 ( _ciph="aes128" ; _fd="private.key" ; _len="2048" ; \ openssl genrsa -${_ciph} -out ${_fd} ${_len} ) ``` ###### 移除私钥的密码保护 ```bash ( _fd="private.key" ; _fd_unp="private_unp.key" ; \ openssl rsa -in ${_fd} -out ${_fd_unp} ) ``` ###### 为现有私钥添加密码保护 ```bash # _ciph: aes128, aes256 ( _ciph="aes128" ; _fd="private.key" ; _fd_pass="private_pass.key" ; \ openssl rsa -${_ciph} -in ${_fd} -out ${_fd_pass} ``` ###### 检查私钥 ```bash ( _fd="private.key" ; \ openssl rsa -check -in ${_fd} ) ``` ###### 从私钥获取公钥 ```bash ( _fd="private.key" ; _fd_pub="public.key" ; \ openssl rsa -pubout -in ${_fd} -out ${_fd_pub} ) ``` ###### 生成私钥和 CSR ```bash ( _fd="private.key" ; _fd_csr="request.csr" ; _len="2048" ; \ openssl req -out ${_fd_csr} -new -newkey rsa:${_len} -nodes -keyout ${_fd} ) ``` ###### 生成 CSR ```bash ( _fd="private.key" ; _fd_csr="request.csr" ; \ openssl req -out ${_fd_csr} -new -key ${_fd} ) ``` ###### 生成 CSR(从现有证书获取元数据) > 其中 `private.key` 是现有的私钥。如你所见,不是从证书(公钥)生成此 CSR。也不是生成"相同"的 CSR,而是一个新的用于请求新证书的 CSR。 ```bash ( _fd="private.key" ; _fd_csr="request.csr" ; _fd_crt="cert.crt" ; \ openssl x509 -x509toreq -in ${_fd_crt} -out ${_fd_csr} -signkey ${_fd} ) ``` ###### 使用 -config 参数生成 CSR ```bash ( _fd="private.key" ; _fd_csr="request.csr" ; \ openssl req -new -sha256 -key ${_fd} -out ${_fd_csr} \ -config <( cat << __EOF__ [req] default_bits = 2048 default_md = sha256 prompt = no distinguished_name = dn req_extensions = req_ext [ dn ] C = "" ST = "" L = "" O = "" OU = "
" CN = "" [ req_ext ] subjectAltName = @alt_names [ alt_names ] DNS.1 = DNS.2 = DNS.3 = __EOF__ )) ``` `[ dn ]` 中的其他值: ``` countryName = "DE" # C= stateOrProvinceName = "Hessen" # ST= localityName = "Keller" # L= postalCode = "424242" # L/postalcode= postalAddress = "Keller" # L/postaladdress= streetAddress = "Crater 1621" # L/street= organizationName = "apfelboymschule" # O= organizationalUnitName = "IT Department" # OU= commonName = "example.com" # CN= emailAddress = "webmaster@example.com" # CN/emailAddress= ``` `oids` 示例(你可能还需要通过在 `[new_oids]` 下添加以下内容来让 OpenSSL 知道 EV 所需的新字段): ``` [req] ... oid_section = new_oids [ new_oids ] postalCode = 2.5.4.17 streetAddress = 2.5.4.9 ``` 完整示例: ```bash ( _fd="private.key" ; _fd_csr="request.csr" ; \ openssl req -new -sha256 -key ${_fd} -out ${_fd_csr} \ -config <( cat << __EOF__ [req] default_bits = 2048 default_md = sha256 prompt = no distinguished_name = dn req_extensions = req_ext oid_section = new_oids [ new_oids ] serialNumber = 2.5.4.5 streetAddress = 2.5.4.9 postalCode = 2.5.4.17 businessCategory = 2.5.4.15 [ dn ] serialNumber=00001111 businessCategory=Private Organization jurisdictionC=DE C=DE ST=Hessen L=Keller postalCode=424242 streetAddress=Crater 1621 O=AV Company OU=IT CN=example.com [ req_ext ] subjectAltName = @alt_names [ alt_names ] DNS.1 = example.com __EOF__ )) ``` 更多信息请参阅这些优秀的解释: - [RFC 5280](https://tools.ietf.org/html/rfc5280) - [How to create multidomain certificates using config files](https://apfelboymchen.net/gnu/notes/openssl%20multidomain%20with%20config%20files.html) - [Generate a multi domains certificate using config files](https://gist.github.com/romainnorberg/464758a6620228b977212a3cf20c3e08) - [Your OpenSSL CSR command is out of date](https://expeditedsecurity.com/blog/openssl-csr-command/) - [OpenSSL example configuration file](https://www.tbs-certificats.com/openssl-dem-server-cert.cnf) - [Object Identifiers (OIDs)](https://www.alvestrand.no/objectid/) - [openssl objects.txt](https://github.com/openssl/openssl/blob/master/crypto/objects/objects.txt) ###### 列出可用的 EC 曲线 ```bash openssl ecparam -list_curves ``` ###### 打印 ECDSA 私钥和公钥 ```bash ( _fd="private.key" ; \ openssl ec -in ${_fd} -noout -text ) # 对于 x25519 仅提取公钥 ( _fd="private.key" ; _fd_pub="public.key" ; \ openssl pkey -in ${_fd} -pubout -out ${_fd_pub} ) ``` ###### 生成 ECDSA 私钥 ```bash # _curve: prime256v1, secp521r1, secp384r1 ( _fd="private.key" ; _curve="prime256v1" ; \ openssl ecparam -out ${_fd} -name ${_curve} -genkey ) # _curve: X25519 ( _fd="private.key" ; _curve="x25519" ; \ openssl genpkey -algorithm ${_curve} -out ${_fd} ) ``` ###### 生成私钥和 CSR (ECC) ```bash # _curve: prime256v1, secp521r1, secp384r1 ( _fd="domain.com.key" ; _fd_csr="domain.com.csr" ; _curve="prime256v1" ; \ openssl ecparam -out ${_fd} -name ${_curve} -genkey ; \ openssl req -new -key ${_fd} -out ${_fd_csr} -sha256 ) ``` ###### 生成自签名证书 ```bash # _len: 2048, 4096 ( _fd="domain.key" ; _fd_out="domain.crt" ; _len="2048" ; _days="365" ; \ openssl req -newkey rsa:${_len} -nodes \ -keyout ${_fd} -x509 -days ${_days} -out ${_fd_out} ) ``` ###### 从现有私钥生成自签名证书 ```bash # _len: 2048, 4096 ( _fd="domain.key" ; _fd_out="domain.crt" ; _days="365" ; \ openssl req -key ${_fd} -nodes \ -x509 -days ${_days} -out ${_fd_out} ) ``` ###### 从现有私钥和 CSR 生成自签名证书 ```bash # _len: 2048, 4096 ( _fd="domain.key" ; _fd_csr="domain.csr" ; _fd_out="domain.crt" ; _days="365" ; \ openssl x509 -signkey ${_fd} -nodes \ -in ${_fd_csr} -req -days ${_days} -out ${_fd_out} ) ``` ###### 生成 DH 公共参数 ```bash ( _dh_size="2048" ; \ openssl dhparam -out /etc/nginx/ssl/dhparam_${_dh_size}.pem "$_dh_size" ) ``` ###### 显示 DH 公共参数 ```bash openssl pkeyparam -in dhparam.pem -text ``` ###### 从 pfx 中提取私钥 ```bash ( _fd_pfx="cert.pfx" ; _fd_key="key.pem" ; \ openssl pkcs12 -in ${_fd_pfx} -nocerts -nodes -out ${_fd_key} ) ``` ###### 从 pfx 中提取私钥和证书 ```bash ( _fd_pfx="cert.pfx" ; _fd_pem="key_certs.pem" ; \ openssl pkcs12 -in ${_fd_pfx} -nodes -out ${_fd_pem} ) ``` ###### 从 p7b 中提取证书 ```bash # PKCS#7 文件不包含私钥。 ( _fd_p7b="cert.p7b" ; _fd_pem="cert.pem" ; \ openssl pkcs7 -inform DER -outform PEM -in ${_fd_p7b} -print_certs > ${_fd_pem}) # 或: openssl pkcs7 -print_certs -in -in ${_fd_p7b} -out ${_fd_pem}) ``` ###### 将 DER 转换为 PEM ```bash ( _fd_der="cert.crt" ; _fd_pem="cert.pem" ; \ openssl x509 -in ${_fd_der} -inform der -outform pem -out ${_fd_pem} ) ``` ###### 将 PEM 转换为 DER ```bash ( _fd_der="cert.crt" ; _fd_pem="cert.pem" ; \ openssl x509 -in ${_fd_pem} -outform der -out ${_fd_der} ) ``` ###### 验证私钥 ```bash ( _fd="private.key" ; \ openssl rsa -noout -text -in ${_fd} ) ``` ###### 验证公钥 ```bash # 1) ( _fd="public.key" ; \ openssl pkey -noout -text -pubin -in ${_fd} ) # 2) ( _fd="private.key" ; \ openssl rsa -inform PEM -noout -in ${_fd} &> /dev/null ; \ if [ $? = 0 ] ; then echo -en "OK\n" ; fi ) ``` ###### 验证证书 ```bash ( _fd="certificate.crt" ; # 格式: pem, cer, crt \ openssl x509 -noout -text -in ${_fd} ) ``` ###### 验证 CSR ```bash ( _fd_csr="request.csr" ; \ openssl req -text -noout -in ${_fd_csr} ) ``` ###### 检查私钥和证书是否匹配 ```bash (openssl rsa -noout -modulus -in private.key | openssl md5 ; \ openssl x509 -noout -modulus -in certificate.crt | openssl md5) | uniq ``` ###### 检查私钥和 CSR 是否匹配 ```bash (openssl rsa -noout -modulus -in private.key | openssl md5 ; \ openssl req -noout -modulus -in request.csr | openssl md5) | uniq ``` ___ ##### 工具: [secure-delete](https://wiki.archlinux.org/index.php/Securely_wipe_disk) - 安全删除 ###### 使用 shred 安全删除 ```bash shred -vfuz -n 10 file shred --verbose --random-source=/dev/urandom -n 1 /dev/sda ``` ###### 使用 scrub 安全删除 ```bash scrub -p dod /dev/sda scrub -p dod -r file ``` ###### 使用 badblocks 安全删除 ```bash badblocks -s -w -t random -v /dev/sda badblocks -c 10240 -s -w -t random -v /dev/sda ``` ###### 使用 secure-delete 安全删除 ```bash srm -vz /tmp/file sfill -vz /local sdmem -v swapoff /dev/sda5 && sswap -vz /dev/sda5 ``` ___ ##### 工具: [dd](https://en.wikipedia.org/wiki/Dd_(Unix)) ###### 让 dd 显示进度状态 ```bash dd status=progress watch --interval 5 killall -USR1 dd ``` ###### 使用 dd 将输出重定向到文件 ```bash echo "string" | dd of=filename ``` ___ ##### 工具: [gpg](https://www.gnupg.org/) ###### 导出公钥 ```bash gpg --export --armor "" > username.pkey ``` * `--export` - 从所有密钥环导出所有密钥或特定密钥 * `-a|--armor` - 创建 ASCII armored 输出 ###### 加密文件 ```bash gpg -e -r "" dump.sql ``` * `-e|--encrypt` - 加密数据 * `-r|--recipient` - 为特定 加密 ###### 解密文件 ```bash gpg -o dump.sql -d dump.sql.gpg ``` * `-o|--output` - 用作输出文件 * `-d|--decrypt` - 解密数据(默认) ###### 搜索接收者 ```bash gpg --keyserver hkp://keyserver.ubuntu.com --search-keys "" ``` * `--keyserver` - 设置特定密钥服务器 * `--search-keys` - 在密钥服务器上搜索密钥 ###### 列出加密文件中的所有数据包 ```bash gpg --batch --list-packets archive.gpg gpg2 --batch --list-packets archive.gpg ``` ___ ##### 工具: [system-other](https://github.com/trimstray/the-book-of-secret-knowledge#tool-system-other) - 系统其他 ###### 从 init 重启系统 ```bash exec /sbin/init 6 ``` ###### 从单用户模式初始化系统 ```bash exec /sbin/init ``` ###### 显示进程的当前工作目录 ```bash readlink -f /proc//cwd ``` ###### 显示已执行命令的实际路径名 ```bash readlink -f /proc//exe ``` ##### 工具: [curl](https://curl.haxx.se) ```bash curl -Iks https://www.google.com ``` * `-I` - 仅显示响应头 * `-k` - 使用 SSL 时的不安全连接 * `-s` - 静默模式(不显示主体) ```bash curl -Iks --location -X GET -A "x-agent" https://www.google.com ``` * `--location` - 跟随重定向 * `-X` - 设置方法 * `-A` - 设置 user-agent ```bash curl -Iks --location -X GET -A "x-agent" --proxy http://127.0.0.1:16379 https://www.google.com ``` * `--proxy [socks5://|http://]` - 设置代理服务器 ```bash curl -o file.pdf -C - https://example.com/Aiju2goo0Ja2.pdf ``` * `-o` - 将输出写入文件 * `-C` - 恢复传输 ###### 查找你的外部 IP 地址(外部服务) ```bash curl ipinfo.io curl ipinfo.io/ip curl icanhazip.com curl ifconfig.me/ip ; echo ``` ###### 重复 URL 请求 ```bash # 使用虚拟查询字符串进行 URL 序列替换: curl -ks https://example.com/?[1-20] # 使用 Shell 'for' 循环: for i in {1..20} ; do curl -ks https://example.com/ ; done ``` ###### 检查特定域名的 DNS 和 HTTP 跟踪及头部 ```bash ### 设置域名和外部 DNS 服务器。 _domain_list=(google.com) ; _dns_list=("8.8.8.8" "1.1.1.1") for _domain in "${_domain_list[@]}" ; do printf '=%.0s' {1..48} echo printf "[\\e[1;32m+\\e[m] resolve: %s\\n" "$_domain" for _dns in "${_dns_list[@]}" ; do # 解析域名。 host "${_domain}" "${_dns}" echo done for _proto in http https ; do printf "[\\e[1;32m+\\e[m] trace + headers: %s://%s\\n" "$_proto" "$_domain" # 获取 trace 和 http 头部。 curl -Iks -A "x-agent" --location "${_proto}://${_domain}" echo done done unset _domain_list _dns_list ``` ___ ##### 工具: [httpie](https://httpie.org/) ```bash http -p Hh https://www.google.com ``` * `-p` - 打印请求和响应头 * `H` - 请求头 * `B` - 请求体 * `h` - 响应头 * `b` - 响应体 ```bash http -p Hh https://www.google.com --follow --verify no ``` * `-F, --follow` - 跟随重定向 * `--verify no` - 跳过 SSL 验证 ```bash http -p Hh https://www.google.com --follow --verify no \ --proxy http:http://127.0.0.1:16379 ``` * `--proxy [http:]` - 设置代理服务器 ##### 工具: [ssh](https://www.openssh.com/) ###### 转义序列 ``` # 支持的转义序列: ~. - 终止连接(以及任何多路复用会话) ~B - 向远程系统发送 BREAK ~C - 打开命令行 ~R - 请求重新生成密钥(仅 SSH 协议 2) ~^Z - 挂起 ssh ~# - 列出转发的连接 ~& - 后台 ssh(当等待连接终止时) ~? - 此消息 ~~ - 通过输入两次来发送转义字符本身 ``` ###### 比较远程文件与本地文件 ```bash ssh user@host cat /path/to/remotefile | diff /path/to/localfile - ``` ###### 通过中间主机进行 SSH 连接 ```bash ssh -t reachable_host ssh unreachable_host ``` ###### 通过 SSH 在远程主机上运行命令 ```bash cat > cmd.txt << __EOF__ cat /etc/hosts __EOF__ ssh host -l user $(&1 | tee -a "${_sesdir}/$(date +%Y%m%d).log" } # 别名: alias ssh='_ssh_sesslog' ``` ###### 使用 Keychain 进行 SSH 登录 ```bash ### 删除 ssh-agent 的所有密钥。 function _scl() { /usr/bin/keychain --clear } ### 添加密钥到 Keychain。 function _scg() { /usr/bin/keychain /path/to/private-key source "$HOME/.keychain/$HOSTNAME-sh" } ``` ###### SSH 登录不处理任何登录脚本 ```bash ssh -tt user@host bash ``` ###### SSH 本地端口转发 示例 1: ```bash # 将本地 2250 端口通过 localhost 转发到 nmap.org:443 host1> ssh -L 2250:nmap.org:443 localhost # 连接到服务: host1> curl -Iks --location -X GET https://localhost:2250 ``` 示例 2: ```bash # 将本地 9051 端口通过 node.d.y 转发到 db.d.x:5432 host1> ssh -nNT -L 9051:db.d.x:5432 node.d.y # 连接到服务: host1> psql -U db_user -d db_dev -p 9051 -h localhost ``` * `-n` - 从 `/dev/null` 重定向 stdin * `-N` - 不执行远程命令 * `-T` - 禁用伪终端分配 ###### SSH 远程端口转发 ```bash # 将本地 9051 端口通过 node.d.y 从 host2 转发到 db.d.x:5432 host1> ssh -nNT -R 9051:db.d.x:5432 node.d.y # 连接到服务: host2> psql -U postgres -d postgres -p 8000 -h localhost ``` ___ ##### 工具: [linux-dev](https://www.tldp.org/LDP/abs/html/devref1.html) ###### 测试到端口的远程连接 ```bash timeout 1 bash -c "//" >/dev/null 2>&1 ; echo $? ``` * `` - 设置远程主机 * `` - 设置目标端口 ###### 使用常用 Bash 工具读写 TCP 或 UDP 套接字 ```bash exec 5<>/dev/tcp//; cat <&5 & cat >&5; exec 5>&- ``` ___ ##### 工具: [tcpdump](http://www.tcpdump.org/) ###### 过滤(接口上的)传入流量(特定 ) ```bash tcpdump -ne -i eth0 -Q in host 192.168.252.1 and port 443 ``` * `-n` - 不转换地址(`-nn` 将不解析主机名或端口) * `-e` - 打印链路层头部 * `-i [iface|any]` - 设置接口 * `-Q|-D [in|out|inout]` - 选择发送/接收方向(`-D` - 旧版 tcpdump 版本) * `host [ip|hostname]` - 设置主机,也可 `[host not]` * `[and|or]` - 设置逻辑 * `port [1-65535]` - 设置端口号,也可 `[port not]` ###### 过滤(接口上的)传入流量(特定 )并写入文件 ```bash tcpdump -ne -i eth0 -Q in host 192.168.252.1 and port 443 -c 5 -w tcpdump.pcap ``` * `-c [num]` - 仅捕获 num 数量的数据包 * `-w [filename]` - 将数据包写入文件,`-r [filename]` - 从文件读取 ###### 捕获所有 ICMP 数据包 ```bash tcpdump -nei eth0 icmp ``` ###### 检查服务使用的协议(TCP 或 UDP) ```bash tcpdump -nei eth0 tcp port 22 -vv -X | egrep "TCP|UDP" ``` ###### 显示 ASCII 文本(用于使用 grep 或其他工具解析输出) ```bash tcpdump -i eth0 -A -s0 port 443 ``` ###### 抓取两个关键字之间的所有内容 ```bash tcpdump -i eth0 port 80 -X | sed -n -e '/username/,/=ldap/ p' ``` ###### 抓取所有明文 HTTP 的用户名和密码 ```bash tcpdump -i eth0 port http -l -A | egrep -i \ 'pass=|pwd=|log=|login=|user=|username=|pw=|passw=|passwd=|password=|pass:|user:|username:|password:|login:|pass |user ' \ --color=auto --line-buffered -B20 ``` ###### 从 HTTP 请求头中提取 HTTP User Agent ```bash tcpdump -ei eth0 -nn -A -s1500 -l | grep "User-Agent:" ``` ###### 仅捕获 HTTP GET 和 POST 数据包 ```bash tcpdump -ei eth0 -s 0 -A -vv \ 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' or 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354' ``` 或简单地: ```bash tcpdump -ei eth0 -s 0 -v -n -l | egrep -i "POST /|GET /|Host:" ``` ###### 轮换捕获文件 ```bash tcpdump -ei eth0 -w /tmp/capture-%H.pcap -G 3600 -C 200 ``` * `-G ` - 每 `` 秒创建一个新的 pcap * `-C ` - 如果当前 pcap 大于 `` 则关闭并打开新的 ###### 按数据包数排名的顶部主机 ```bash tcpdump -ei enp0s25 -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20 ``` ###### 排除任何 RFC 1918 私有地址 ```bash tcpdump -nei eth0 'not (src net (10 or 172.16/12 or 192.168/16) and dst net (10 or 172.16/12 or 192.168/16))' ``` ___ ##### 工具: [tcpick](http://tcpick.sourceforge.net/) ###### 实时分析数据包 ```bash while true ; do tcpick -a -C -r dump.pcap ; sleep 2 ; clear ; done ``` ___ ##### 工具: [ngrep](http://ngrep.sourceforge.net/usage.html) ```bash ngrep -d eth0 "www.domain.com" port 443 ``` * `-d [iface|any]` - 设置接口 * `[domain]` - 设置主机名 * `port [1-65535]` - 设置端口号 ```bash ngrep -d eth0 "www.domain.com" src host 10.240.20.2 and port 443 ``` * `(host [ip|hostname])` - 按 IP 或主机名过滤 * `(port [1-65535])` - 按端口号过滤 ```bash ngrep -d eth0 -qt -O ngrep.pcap "www.domain.com" port 443 ``` * `-q` - 安静模式(仅有效载荷) * `-t` - 添加时间戳 * `-O [filename]` - 将输出保存到文件,`-I [filename]` - 从文件读取 ```bash ngrep -d eth0 -qt 'HTTP' 'tcp' ``` * `HTTP` - 显示 HTTP 头部 * `tcp|udp` - 设置协议 * `[src|dst] host [ip|hostname]` - 设置特定节点的方向 ```bash ngrep -l -q -d eth0 -i "User-Agent: curl*" ``` * `-l` - stdout 行缓冲 * `-i` - 不区分大小写搜索 ___ ##### 工具: [hping3](http://www.hping.org/) ```bash hping3 -V -p 80 -s 5050 www.google.com ``` * `-V|--verbose` - 详细模式 * `-p|--destport` - 设置目标端口 * `-s|--baseport` - 设置源端口 * `` - 设置扫描类型 * `-F|--fin` - 设置 FIN 标志,端口开放则无回复 * `-S|--syn` - 设置 SYN 标志 * `-P|--push` - 设置 PUSH 标志 * `-A|--ack` - 设置 ACK 标志(当 ping 被阻止时使用,端口开放则返回 RST 响应) * `-U|--urg` - 设置 URG 标志 * `-Y|--ymas` - 设置 Y 未使用标志(0x80 - nullscan),端口开放则无回复 * `-M 0 -UPF` - 设置 TCP 序列号和扫描类型(URG+PUSH+FIN),端口开放则无回复 ```bash hping3 -V -c 1 -1 -C 8 www.google.com ``` * `-c [num]` - 数据包计数 * `-1` - 设置 ICMP 模式 * `-C|--icmptype [icmp-num]` - 设置 icmp 类型(默认 icmp-echo = 8) ```bash hping3 -V -c 1000000 -d 120 -S -w 64 -p 80 --flood --rand-source ``` * `--flood` - 尽可能快地发送数据包(不显示回复) * `--rand-source` - 随机源地址模式 * `-d --data` - 数据大小 * `-w|--win` - winsize(默认 64) ___ ##### 工具: [nmap](https://nmap.org/) ###### Ping 扫描网络 ```bash nmap -sP 192.168.0.0/24 ``` ###### 仅显示开放端口 ```bash nmap -F --open 192.168.0.0/24 ``` ###### 全 TCP 端口扫描及服务版本检测 ```bash nmap -p 1-65535 -sV -sS -T4 192.168.0.0/24 ``` ###### Nmap 扫描并将输出传递给 Nikto ```bash nmap -p80,443 192.168.0.0/24 -oG - | nikto.pl -h - ``` ###### 使用 Nmap NSE 脚本堆栈侦察特定 ip:service ```bash # 设置变量: _hosts="192.168.250.10" _ports="80,443" # 设置 Nmap NSE 脚本堆栈: _nmap_nse_scripts="+dns-brute,\ +http-auth-finder,\ +http-chrono,\ +http-cookie-flags,\ +http-cors,\ +http-cross-domain-policy,\ +http-csrf,\ +http-dombased-xss,\ +http-enum,\ +http-errors,\ +http-git,\ +http-grep,\ +http-internal-ip-disclosure,\ +http-jsonp-detection,\ +http-malware-host,\ +http-methods,\ +http-passwd,\ +http-phpself-xss,\ +http-php-version,\ +http-robots.txt,\ +http-sitemap-generator,\ +http-shellshock,\ +http-stored-xss,\ +http-title,\ +http-unsafe-output-escaping,\ +http-useragent-tester,\ +http-vhosts,\ +http-waf-detect,\ +http-waf-fingerprint,\ +http-xssed,\ +traceroute-geolocation.nse,\ +ssl-enum-ciphers,\ +whois-domain,\ +whois-ip" # 设置 Nmap NSE 脚本参数: _nmap_nse_scripts_args="dns-brute.domain=${_hosts},http-cross-domain-policy.domain-lookup=true," _nmap_nse_scripts_args+="http-waf-detect.aggro,http-waf-detect.detectBodyChanges," _nmap_nse_scripts_args+="http-waf-fingerprint.intensive=1" # 执行扫描: nmap --script="$_nmap_nse_scripts" --script-args="$_nmap_nse_scripts_args" -p "$_ports" "$_hosts" ``` ___ ##### 工具: [netcat](http://netcat.sourceforge.net/) ```bash nc -kl 5000 ``` * `-l` - 监听传入连接 * `-k` - 客户端断开后继续监听 * `>filename.out` - 将接收到的数据保存到文件(可选) ```bash nc 192.168.0.1 5051 < filename.in ``` * `< filename.in` - 发送数据到远程主机 ```bash nc -vz 10.240.30.3 5000 ``` * `-v` - 详细输出 * `-z` - 扫描监听守护进程 ```bash nc -vzu 10.240.30.3 1-65535 ``` * `-u` - 仅扫描 udp 端口 ###### 传输数据文件(归档) ```bash server> nc -l 5000 | tar xzvfp - client> tar czvfp - /path/to/dir | nc 10.240.30.3 5000 ``` ###### 启动远程 Shell ```bash # 1) server> nc -l 5000 -e /bin/bash client> nc 10.240.30.3 5000 # 2) server> rm -f /tmp/f; mkfifo /tmp/f server> cat /tmp/f | /bin/bash -i 2>&1 | nc -l 127.0.0.1 5000 > /tmp/f client> nc 10.240.30.3 5000 ``` ###### 简单文件服务器 ```bash while true ; do nc -l 5000 | tar -xvf - ; done ``` ###### 简单的极简 HTTP 服务器 ```bash while true ; do nc -l -p 1500 -c 'echo -e "HTTP/1.1 200 OK\n\n $(date)"' ; done ``` ###### 简单 HTTP 服务器 > 每次请求后重启 Web 服务器 - 移除 `while` 条件则仅允许单次连接。 ```bash cat > index.html << __EOF__

Hello! It's a site.

__EOF__ ``` ```bash server> while : ; do \ (echo -ne "HTTP/1.1 200 OK\r\nContent-Length: $(wc -c /" <"$_sent" & sed "s/^/<= /" <"$_recv" & nc -l -p "$_listen_port" <"$_back" | \ tee "$_sent" | \ nc "$_bk_host" "$_bk_port" | \ tee "$_recv" >"$_back" ``` ```bash server> chmod +x nc-proxy && ./nc-proxy 8080 192.168.252.10:8000 lport: 8080 bk_host: 192.168.252.10 bk_port: 8000 client> http -p h 10.240.30.3:8080 HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: max-age=31536000 Content-Length: 2748 Content-Type: text/html; charset=utf-8 Date: Sun, 01 Jul 2018 20:12:08 GMT Last-Modified: Sun, 01 Apr 2018 21:53:37 GMT ``` ###### 创建一次性 TCP 或 UDP 代理 ```bash ### TCP -> TCP nc -l -p 2000 -c "nc [ip|hostname] 3000" ### TCP -> UDP nc -l -p 2000 -c "nc -u [ip|hostname] 3000" ### UDP -> UDP nc -l -u -p 2000 -c "nc -u [ip|hostname] 3000" ### UDP -> TCP nc -l -u -p 2000 -c "nc [ip|hostname] 3000" ``` ___ ##### 工具: [gnutls-cli](https://gnutls.org/manual/html_node/gnutls_002dcli-Invocation.html) ###### 测试到远程主机的连接(支持 SNI) ```bash gnutls-cli -p 443 google.com ``` ###### 测试到远程主机的连接(不支持 SNI) ```bash gnutls-cli --disable-sni -p 443 google.com ``` ___ ##### 工具: [socat](http://www.dest-unreach.org/socat/doc/socat.html) ###### 测试到端口的远程连接 ```bash socat - TCP4:10.240.30.3:22 ``` * `-` - 标准输入 (STDIO) * `TCP4:` - 设置具有特定参数的 tcp4 连接 * `[hostname|ip]` - 设置主机名/ip * `[1-65535]` - 设置端口号 ###### 在 Linux 下将 TCP 流量重定向到 UNIX 域套接字 ```bash socat TCP-LISTEN:1234,bind=127.0.0.1,reuseaddr,fork,su=nobody,range=127.0.0.0/8 UNIX-CLIENT:/tmp/foo ``` * `TCP-LISTEN:` - 设置具有特定参数的 tcp 监听 * `[1-65535]` - 设置端口号 * `bind=[hostname|ip]` - 设置绑定的主机名/ip * `reuseaddr` - 允许其他套接字绑定到某个地址 * `fork` - 保持父进程尝试产生更多连接 * `su=nobody` - 设置用户 * `range=[ip-range]` - IP 范围 * `UNIX-CLIENT:` - 与指定的对等套接字通信 * `filename` - 定义套接字 ___ ##### 工具: [p0f](http://lcamtuf.coredump.cx/p0f3/) ###### 将接口设置为混杂模式并将流量转储到日志文件 ```bash p0f -i enp0s25 -p -d -o /dump/enp0s25.log ``` * `-i` - 监听指定接口 * `-p` - 将接口设置为混杂模式 * `-d` - fork 到后台 * `-o` - 输出文件 ___ ##### 工具: [netstat](https://en.wikipedia.org/wiki/Netstat) ###### 为每个主机的连接数绘制图形 ```bash netstat -an | awk '/ESTABLISHED/ { split($5,ip,":"); if (ip[1] !~ /^$/) print ip[1] }' | \ sort | uniq -c | awk '{ printf("%s\t%s\t",$2,$1) ; for (i = 0; i < $1; i++) {printf("*")}; print "" }' ``` ###### 监控特定端口的开放连接,包括监听、计数并按 IP 排序 ```bash watch "netstat -plan | grep :443 | awk {'print \$5'} | cut -d: -f 1 | sort | uniq -c | sort -nk 1" ``` ###### 从本地 IPv4 监听端口抓取横幅 ```bash netstat -nlt | grep 'tcp ' | grep -Eo "[1-9][0-9]*" | xargs -I {} sh -c "echo "" | nc -v -n -w1 127.0.0.1 {}" ``` ___ ##### 工具: [rsync](https://en.wikipedia.org/wiki/Rsync) ###### 使用 sudo 作为 root 远程同步数据 ```bash rsync --rsync-path 'sudo rsync' username@hostname:/path/to/dir/ /local/ ``` ___ ##### 工具: [host](https://en.wikipedia.org/wiki/Host_(Unix)) ###### 解析域名(使用外部 DNS 服务器) ```bash host google.com 9.9.9.9 ``` ###### 检查域管理员(SOA 记录) ```bash host -t soa google.com 9.9.9.9 ``` ___ ##### 工具: [dig](https://en.wikipedia.org/wiki/Dig_(command)) ###### 解析域名(简短输出) ```bash dig google.com +short ``` ###### 查询特定域的 NS 记录 ```bash dig @9.9.9.9 google.com NS ``` ###### 仅查询答案部分 ```bash dig google.com +nocomments +noquestion +noauthority +noadditional +nostats ``` ###### 查询所有 DNS 记录 ```bash dig google.com ANY +noall +answer ``` ###### DNS 反向查询 ```bash dig -x 172.217.16.14 +short ``` ___ ##### 工具: [certbot](https://certbot.eff.org/) ###### 生成多域名证书 ```bash certbot certonly -d example.com -d www.example.com ``` ###### 生成通配符证书 ```bash certbot certonly --manual --preferred-challenges=dns -d example.com -d *.example.com ``` ###### 使用 4096 位私钥生成证书 ```bash certbot certonly -d example.com -d www.example.com --rsa-key-size 4096 ``` ___ ##### 工具: [network-other](https://github.com/trimstray/the-book-of-secret-knowledge#tool-network-other) - 网络其他 ###### 获取特定 AS(自治系统)的所有子网 ```bash AS="AS32934" whois -h whois.radb.net -- "-i origin ${AS}" | \ grep "^route:" | \ cut -d ":" -f2 | \ sed -e 's/^[ \t]//' | \ sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 | \ cut -d ":" -f2 | \ sed -e 's/^[ \t]/allow /' | \ sed 's/$/;/' | \ sed 's/allow */subnet -> /g' ``` ###### 使用 curl 和 jq 从 dns.google.com 解析域名 ```bash _dname="google.com" ; curl -s "https://dns.google.com/resolve?name=${_dname}&type=A" | jq . ``` ##### 工具: [git](https://git-scm.com/) ###### 用于体面查看仓库的日志别名 ```bash # 1) git log --oneline --decorate --graph --all # 2) git log --graph \ --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' \ --abbrev-commit ``` ___ ##### 工具: [python](https://www.python.org/) ###### 静态 HTTP Web 服务器 ```bash # Python 3.x python3 -m http.server 8000 --bind 127.0.0.1 # Python 2.x python -m SimpleHTTPServer 8000 ``` ###### 带 SSL 支持的静态 HTTP Web 服务器 ```bash # Python 3.x from http.server import HTTPServer, BaseHTTPRequestHandler import ssl httpd = HTTPServer(('localhost', 4443), BaseHTTPRequestHandler) httpd.socket = ssl.wrap_socket (httpd.socket, keyfile="path/to/key.pem", certfile='path/to/cert.pem', server_side=True) httpd.serve_forever() # Python 2.x import BaseHTTPServer, SimpleHTTPServer import ssl httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler) httpd.socket = ssl.wrap_socket (httpd.socket, keyfile="path/tp/key.pem", certfile='path/to/cert.pem', server_side=True) httpd.serve_forever() ``` ###### 编码 Base64 ```bash python -m base64 -e <<< "sample string" ``` ###### 解码 Base64 ```bash python -m base64 -d <<< "dGhpcyBpcyBlbmNvZGVkCg==" ``` ##### 工具: [awk](http://www.grymoire.com/Unix/Awk.html) ###### 搜索匹配的行 ```bash # egrep foo awk '/foo/' filename ``` ###### 搜索不匹配的行 ```bash # egrep -v foo awk '!/foo/' filename ``` ###### 打印带行号的匹配行 ```bash # egrep -n foo awk '/foo/{print FNR,$0}' filename ``` ###### 打印最后一列 ```bash awk '{print $NF}' filename ``` ###### 查找所有超过 80 个字符的行 ```bash awk 'length($0)>80{print FNR,$0}' filename ``` ###### 仅打印少于 80 个字符的行 ```bash awk 'length < 80' filename ``` ###### 在文件中打印双倍空行 ```bash awk '1; { print "" }' filename ``` ###### 打印行号 ```bash awk '{ print FNR "\t" $0 }' filename awk '{ printf("%5d : %s\n", NR, $0) }' filename # 以更漂亮的方式 ``` ###### 仅为非空行打印行号 ```bash awk 'NF { $0=++a " :" $0 }; { print }' filename ``` ###### 打印匹配正则表达式的行及其后两行(i=5) ```bash awk '/foo/{i=5+1;}{if(i){i--; print;}}' filename ``` ###### 打印从匹配 'server {' 开始到匹配 '}' 结束的行 ```bash awk '/server {/,/}/' filename ``` ###### 打印带分隔符的多列 ```bash awk -F' ' '{print "ip:\t" $2 "\n port:\t" $3' filename ``` ###### 移除空行 ```bash awk 'NF > 0' filename # 替代方案: awk NF filename ``` ###### 删除尾部空白(空格、制表符) ```bash awk '{sub(/[ \t]*$/, "");print}' filename ``` ###### 删除前导空白 ```bash awk '{sub(/^[ \t]+/, ""); print}' filename ``` ###### 移除连续重复行 ```bash # uniq awk 'a !~ $0{print}; {a=$0}' filename ``` ###### 在不排序的情况下移除文件中的重复条目 ```bash awk '!x[$0]++' filename ``` ###### 排除多列 ```bash awk '{$1=$3=""}1' filename ``` ###### 对于匹配正则表达式的行,将 foo 替换为 bar ```bash awk '/regexp/{gsub(/foo/, "bar")};{print}' filename ``` ###### 在匹配行的开头添加一些字符 ```bash awk '/regexp/{sub(/^/, "++++"); print;next;}{print}' filename ``` ###### 获取 Apache 日志的最后一小时 ```bash awk '/'$(date -d "1 hours ago" "+%d\\/%b\\/%Y:%H:%M")'/,/'$(date "+%d\\/%b\\/%Y:%H:%M")'/ { print $0 }' \ /var/log/httpd/access_log ``` ___ ##### 工具: [sed](http://www.grymoire.com/Unix/Sed.html) ###### 打印文件中的特定行 ```bash sed -n 10p /path/to/file ``` ###### 从文件中删除特定行 ```bash sed -i 10d /path/to/file # 替代方案 (BSD): sed -i'' 10d /path/to/file ``` ###### 从文件中删除连续范围的行 ```bash sed -i -re ',d' ``` ###### 将换行符替换为空格 ```bash sed ':a;N;$!ba;s/\n/ /g' /path/to/file # 跨平台兼容语法: sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' /path/to/file ``` - `:a` 创建标签 `a` - `N` 将下一行追加到模式空间 - `$!` 如果不是最后一行,ba 分支(跳转到)标签 `a` - `s` 替换,`/\n/` 换行符的正则表达式,`/ /` 替换为空格,`/g` 全局匹配(尽可能多次) 替代方案: ```bash # perl 版本(类似 sed 的速度): perl -p -e 's/\n/ /' /path/to/file # bash 版本(较慢): while read line ; do printf "%s" "$line " ; done < file ``` ###### 删除字符串及其后 +N 行 ```bash sed '/start/,+4d' /path/to/file ``` ___ ##### 工具: [grep](http://www.grymoire.com/Unix/Grep.html) ###### 在当前目录所有文件中搜索 "pattern" ```bash grep -rn "pattern" grep -RnisI "pattern" * fgrep "pattern" * -R ``` ###### 仅显示匹配多个模式的结果 ```bash grep 'INFO*'\''WARN' filename grep 'INFO\|WARN' filename grep -e INFO -e WARN filename grep -E '(INFO|WARN)' filename egrep "INFO|WARN" filename ``` ###### 排除多个模式 ```bash grep -vE '(error|critical|warning)' filename ``` ###### 显示文件中没有注释的数据 ```bash grep -v ^[[:space:]]*# filename ``` ###### 显示文件中没有注释和空行的数据 ```bash egrep -v '#|^$' filename ``` ###### 显示包含连字符/短划线的字符串 ```bash grep -e -- filename grep -- -- filename grep "\-\-" filename ``` ###### 从文件中删除空行并将输出保存到新文件 ```bash grep . filename > newfilename ``` ##### 工具: [perl](https://www.perl.org/) ###### 搜索和替换(原地编辑) ```bash perl -i -pe's/SEARCH/REPLACE/' filename ``` ###### 编辑 `*.conf` 文件将所有 foo 改为 bar(并备份原始文件) ```bash perl -p -i.orig -e 's/\bfoo\b/bar/g' *.conf ``` ###### 打印 `*.conf` 文件的前 20 行 ```bash perl -pe 'exit if $. > 20' *.conf ``` ###### 搜索第 10 到 20 行 ```bash perl -ne 'print if 10 .. 20' filename ``` ###### 删除前 10 行(并备份原始文件) ```bash perl -i.orig -ne 'print unless 1 .. 10' filename ``` ###### 删除 foo 和 bar 之间行之外的所有内容(并备份原始文件) ```bash perl -i.orig -ne 'print unless /^foo$/ .. /^bar$/' filename ``` ###### 将多个连续空行缩减为单行 ```bash perl -p -i -00pe0 filename ``` ###### 将制表符转换为空格(1t = 2sp) ```bash perl -p -i -e 's/\t/ /g' filename ``` ###### 从文件读取输入并报告行数和字符数 ```bash perl -lne '$i++; $in += length($_); END { print "$i lines, $in characters"; }' filename ``` #### Shell 技巧  [[目录]](#anger-table-of-contents) ##### 目录 * [获取干净 Shell](#获取干净-shell) * [历史展开](#历史展开) * [花括号展开](#花括号展开) * [参数展开](#参数展开) * [目录导航](#目录导航) * [重定向进阶](#重定向进阶) * [trap 信号捕获](#trap-信号捕获) * [Shell 选项](#shell-选项) * [调试技巧](#调试技巧) * [后台任务管理](#后台任务管理) * [ANSI 颜色和光标控制](#ansi-颜色和光标控制) * [Here String](#here-string) * [特殊变量速查](#特殊变量速查) ###### 获取干净 Shell 当你获得一个 Shell 时,它通常不太干净,但按照以下步骤操作后,你将拥有一个相当干净且舒适的 Shell 工作环境。 1) `script /dev/null -c bash` 2) Ctrl-Z(将其发送到后台) 3) `stty raw -echo; fg`(将 shell 返回到前台) 4) `reset`(重置终端) 5) `xterm`(当被询问终端类型时) 6) `export TERM=xterm; export SHELL=bash` ###### 历史展开 (History Expansion) ```bash !! # 重复上一条命令 sudo !! # 用 sudo 重新执行上一条命令 !$ # 上一条命令的最后一个参数 !^ # 上一条命令的第一个参数 !* # 上一条命令的所有参数 !:n # 上一条命令的第 n 个参数 (0-indexed,命令本身为 :0) !:- # 上一条命令的所有参数(不含命令名) ! # 执行历史中第 n 条命令 ! # 执行最近以 string 开头的命令 ^old^new # 将上一条命令中的 old 替换为 new 并执行 !:gs/old/new # 将上一条命令中所有 old 替换为 new 并执行 ``` ###### 花括号展开 (Brace Expansion) ```bash echo {1..10} # 1 2 3 4 5 6 7 8 9 10 echo {01..10} # 01 02 03 ... 10 (补齐前导零) echo {a..z} # a b c d ... z echo {a..z..2} # a c e g ... y (步长为 2) echo {z..a..3} # z w t q n k h e b (逆向递减) echo {1..10..3} # 1 4 7 10 # 枚举展开 echo file.{txt,md,log} # file.txt file.md file.log echo backup-{2020..2025}-{01..12} # 组合展开 # 批量创建目录 mkdir -p project/{src,test,docs}/{js,python} ``` ###### 参数展开 (Parameter Expansion) ```bash var="hello world" echo "${var}" # hello world echo "${#var}" # 11 (字符串长度) # 默认值 echo "${var:-default}" # 如果 var 未设置或为空,返回 default(不修改变量) echo "${var:=default}" # 如果 var 未设置或为空,赋值为 default 并返回 echo "${var:+replaced}" # 如果 var 已设置且非空,返回 replaced(替代值) echo "${var:?变量未设置}" # 如果 var 未设置或为空,打印错误信息并退出 # 子串和切片 echo "${var:6}" # world (从第 6 个字符开始) echo "${var:0:5}" # hello (从 0 开始取 5 个字符) echo "${var: -5}" # world (从末尾倒数 5 个字符,注意冒号后的空格) # 前缀删除 echo "${var#hello }" # world (删除最短匹配的前缀) echo "${var##h*o}" # rld (删除最长匹配的前缀) # 后缀删除 echo "${var%world}" # hello (删除最短匹配的后缀) echo "${var%%w*}" # hello (删除最长匹配的后缀) # 全局替换 echo "${var/hello/hi}" # hi world (替换第一个匹配) echo "${var//o/O}" # hellO wOrld (替换所有匹配) echo "${var//[aeiou]/_}" # h_ll_ w_rld (替换任意元音) # 大小写转换 (Bash 4+) echo "${var^^}" # HELLO WORLD (全部大写) echo "${var,,}" # hello world (全部小写) echo "${var^}" # Hello world (仅首字母大写) echo "${var,}" # hello world (仅首字母小写) echo "${var~~}" # hELLO WORLD (大小写反转) # 间接引用 var_name="PATH" echo "${!var_name}" # 输出 PATH 变量的值(间接展开) ``` ###### 目录导航 ```bash # 目录栈操作 pushd /path/to/dir # 切换到目录并推入栈 popd # 弹出栈顶目录并切换 dirs # 查看目录栈 dirs -v # 带编号查看目录栈 pushd +2 # 切换到栈中第 2 个目录(dirs -v 显示的编号) # 快速返回 cd - # 返回上一个工作目录 cd "$OLDPWD" # 同上,使用变量 # CDPATH 搜索路径 export CDPATH=.:~:/var/log:/etc # 设置后,cd nginx 会自动在 CDPATH 目录下搜索 nginx 子目录 # 创建目录并立即进入 mkcd() { mkdir -p "$1" && cd "$1"; } ``` ###### 重定向进阶 ```bash # 进程替换 - 将命令输出当作文件使用 diff <(ls dir1) <(ls dir2) # 比较两个目录 comm -23 <(sort file1) <(sort file2) # 找出 file1 中独有的行 # 将 stdout 和 stderr 分别通过不同命令处理 command > >(processor1) 2> >(processor2) # exec 重定向 - 整个脚本的输入输出重定向 exec > >(tee -a /var/log/script.log) 2>&1 # 所有输出同时到屏幕和日志 exec 3>&1 >stdout.log 2>&1 # 保存原始 stdout 到 fd 3 # 仅捕获 stderr(丢弃 stdout) command 2>&1 >/dev/null | grep "error" # 同时输出到文件和 stdout command 2>&1 | tee output.log # 多文件输出 command | tee file1 file2 >/dev/null # 快速交换 stdout 和 stderr command 3>&1 1>&2 2>&3 ``` ###### trap 信号捕获 ```bash # 退出时清理临时文件 trap 'rm -rf "$TMPDIR"' EXIT # 捕获 Ctrl+C 优雅退出 trap 'echo -e "\n被中断,正在清理..."; exit 130' INT # 捕获多个信号 cleanup() { rm -f "$LOCKFILE"; echo "已清理"; } trap cleanup EXIT INT TERM # 错误时打印行号(调试用) trap 'echo "错误发生在第 ${LINENO} 行,命令: ${BASH_COMMAND}"' ERR # 最常见的信号 # INT (2) - Ctrl+C 中断 # TERM (15) - kill 默认信号 # EXIT (0) - 脚本正常或异常退出时(非信号) # HUP (1) - 终端挂起 # QUIT (3) - Ctrl+\ 退出并生成 core dump # KILL (9) - 无法被捕获,强制终止 ``` ###### Shell 选项 ```bash # 编写健壮脚本的必备选项 set -e # 任何命令返回非零状态立即退出 set -u # 引用未定义变量时退出 set -o pipefail # 管道中任一命令失败则整体失败($? 为最后一个失败命令的状态) set -E # ERR trap 在函数和子 shell 中也能捕获 # 常用组合 set -euo pipefail # 调试选项 set -x # 打印每条执行的命令(展开变量后) set -v # 打印读取的每一行(展开变量前) # shopt 扩展选项 shopt -s extglob # 启用扩展模式匹配: !(pattern) @(pattern) +(pattern) ?(pattern) shopt -s globstar # 启用 ** 递归匹配 shopt -s dotglob # 通配符匹配隐藏文件 shopt -s autocd # 输入目录名自动 cd shopt -s dirspell # 拼写纠错目录名 shopt -s cdspell # cd 时纠错 shopt -s histappend # 追加而非覆盖历史文件 shopt -s checkwinsize # 每次命令后检查窗口大小 ``` ###### 调试技巧 ```bash # 运行脚本时调试 bash -x script.sh # 打印每条执行命令 bash -n script.sh # 仅检查语法,不执行 # 在脚本中局部调试 set -x # 开启跟踪 # ... 需要调试的代码段 set +x # 关闭跟踪 # 定制 PS4 显示文件名、行号和函数名 export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' # 使用 trap DEBUG 单步调试 trap 'read -p "[$LINENO] \$BASH_COMMAND"' DEBUG # 使用 trap ERR 在错误处进入 Shell trap '(echo "错误行: $LINENO"; exec bash -i)' ERR # 打印函数调用栈 _trace() { for i in ${!FUNCNAME[@]}; do echo " #$i: ${FUNCNAME[$i]} (${BASH_SOURCE[$i]}:${BASH_LINENO[$i]})" done } trap '_trace' ERR ``` ###### 后台任务管理 ```bash # 后台运行 command & # 放入后台运行 nohup command & # 忽略 SIGHUP,退出终端后继续运行 command &>/dev/null & # 静默后台运行 disown # 脱离最近的后台任务(当前 shell 退出后继续运行) # 管理任务 jobs # 列出当前 shell 的后台任务 jobs -l # 同时显示 PID fg # 将最近的后台任务调回前台 fg %1 # 将指定任务号调回前台 bg %1 # 让暂停的任务在后台继续运行 # 等待与脱离 wait # 等待所有后台任务完成 wait $pid # 等待指定 PID 的任务 disown -a # 脱离所有后台任务 disown -h %1 # 标记任务不接收 SIGHUP # 并行处理多个任务并等待全部完成 for host in host1 host2 host3; do ssh "$host" some_command & done wait echo "全部完成" ``` ###### ANSI 颜色和光标控制 ```bash # 颜色定义 rst="\e[0m" ; red="\e[0;31m" ; green="\e[0;32m" yel="\e[0;33m" ; blue="\e[0;34m" ; mag="\e[0;35m" cya="\e[0;36m" ; wht="\e[0;37m" # 粗体颜色 bred="\e[1;31m" ; bgreen="\e[1;32m" ; bwhite="\e[1;37m" # 背景色 bg_red="\e[41m" ; bg_green="\e[42m" ; bg_yellow="\e[43m" bg_blue="\e[44m" ; bg_magenta="\e[45m" ; bg_cyan="\e[46m" # 使用示例 echo -e "${red}错误:${rst} 文件未找到" echo -e "${bgreen}成功:${rst} 构建完成" # 光标控制 echo -ne "\e[?25l" # 隐藏光标 echo -ne "\e[?25h" # 显示光标 echo -ne "\e[2J" # 清屏 echo -ne "\e[1;1H" # 光标移动到 (1,1) echo -ne "\e[K" # 清除从光标到行尾 echo -ne "\e[1A" # 光标上移 1 行 # 原地刷新行(简易旋转指示器) spinner=('|' '/' '-' '\') for ((i=0; i<20; i++)); do echo -ne "\r${spinner[i%4]} 处理中..." sleep 0.1 done echo -ne "\r完成! \n" ``` ###### Here String ```bash # Here String 将字符串通过 stdin 传递,避免 echo | 产生的子 shell grep pattern <<<"$variable" read -r a b c <<<"one two three" # 将多行文本作为单个字符串传递 cat <<<"第 1 行 第 2 行 第 3 行" # 与 Here Doc 对比 — Here Doc 用于多行模板 # 带引号的 'EOF' 阻止变量展开 cat <<'EOF' 此处 $variables 和 $(commands) 不会被展开 EOF # 不带引号的 EOF 会展开变量 cat <[目录]](#anger-table-of-contents) ##### 目录 - [Domain resolve](#domain-resolve) - 域名解析 - [Get ASN](#get-asn) - 获取 ASN - [日志函数](#日志函数) - 带级别和颜色的日志 - [重试函数](#重试函数) - 失败自动重试 - [进度条](#进度条) - 纯 Bash 进度条 - [URL 编解码](#url-编解码) - urlencode / urldecode - [随机密码生成](#随机密码生成) - 随机密码/字符串 - [锁文件机制](#锁文件机制) - 防止脚本重复执行 - [临时目录管理](#临时目录管理) - 创建并自动清理 - [命令依赖检查](#命令依赖检查) - 检查必需命令 - [执行计时](#执行计时) - 测量命令执行耗时 - [JSON 快速提取](#json-快速提取) - 不依赖 jq 的值提取 - [路径规范化](#路径规范化) - 获取绝对路径 - [配置文件解析](#配置文件解析) - 解析 key=value 配置 - [数组工具](#数组工具) - 去重和包含检查 - [批量重命名](#批量重命名) - 安全批量重命名 ###### 域名解析 (Domain resolve) ```bash # 依赖: # - curl # - jq function DomainResolve() { local _host="$1" local _curl_base="curl --request GET" local _timeout="15" _host_ip=$($_curl_base -ks -m "$_timeout" "https://dns.google.com/resolve?name=${_host}&type=A" | \ jq '.Answer[0].data' | tr -d "\"" 2>/dev/null) if [[ -z "$_host_ip" ]] || [[ "$_host_ip" == "null" ]] ; then echo -en "域名解析失败。\\n" else echo -en "$_host > $_host_ip\\n" fi } ``` 示例: ```bash shell> DomainResolve nmap.org nmap.org > 45.33.49.119 shell> DomainResolve nmap.org 域名解析失败。 ``` ###### 获取 ASN (Get ASN) ```bash # 依赖: # - curl function GetASN() { local _ip="$1" local _curl_base="curl --request GET" local _timeout="15" _asn=$($_curl_base -ks -m "$_timeout" "http://ip-api.com/line/${_ip}?fields=as") _state=$(echo $?) if [[ -z "$_ip" ]] || [[ "$_ip" == "null" ]] || [[ "$_state" -ne 0 ]]; then echo -en "ASN 获取失败。\\n" else echo -en "$_ip > $_asn\\n" fi } ``` 示例: ```bash shell> GetASN 1.1.1.1 1.1.1.1 > AS13335 Cloudflare, Inc. shell> GetASN 0.0.0.0 ASN 获取失败。 ``` ###### 日志函数 ```bash # 带级别、时间戳和颜色的日志函数 # 依赖: 无 _log_ts() { date +"%Y-%m-%d %H:%M:%S"; } log_info() { echo -e "\e[0;32m[INFO]\e[0m $(_log_ts) - $*"; } log_warn() { echo -e "\e[0;33m[WARN]\e[0m $(_log_ts) - $*"; } log_error() { echo -e "\e[0;31m[ERROR]\e[0m $(_log_ts) - $*"; } log_debug() { [[ -n "${DEBUG:-}" ]] && echo -e "\e[0;34m[DEBUG]\e[0m $(_log_ts) - $*"; } log_fatal() { echo -e "\e[1;31m[FATAL]\e[0m $(_log_ts) - $*"; exit 1; } # 支持直接管道输入 log_info "正在处理文件..." log_warn "磁盘使用率已超过 80%" log_error "连接超时,请检查网络" log_debug "当前变量值: var=$myvar" ``` ###### 重试函数 ```bash # 失败后自动重试,带指数退避 # 依赖: 无 retry() { local max_attempts="${1:-3}" local delay="${2:-2}" local attempt=1 shift 2 2>/dev/null || true while (( attempt <= max_attempts )); do echo -ne "\e[0;34m[RETRY]\e[0m 第 ${attempt}/${max_attempts} 次尝试... " if "$@"; then echo -e "\e[0;32m成功\e[0m" return 0 fi echo -e "\e[0;31m失败\e[0m" if (( attempt < max_attempts )); then sleep "$delay" delay=$(( delay * 2 )) # 指数退避 fi (( attempt++ )) done echo -e "\e[0;31m[ERROR]\e[0m 重试 ${max_attempts} 次后仍然失败" return 1 } # 示例: retry 5 1 curl -s https://example.com/api retry 3 2 ssh remote_host 'some_command' ``` ###### 进度条 ```bash # 纯 Bash 实现,不依赖外部命令 # 依赖: 无 progress_bar() { local current="${1:-0}" local total="${2:-100}" local width="${3:-40}" local percent=$(( current * 100 / total )) local filled=$(( width * percent / 100 )) local empty=$(( width - filled )) printf -v bar "%${filled}s" ""; bar="${bar// /#}" printf -v space "%${empty}s" "" printf "\r[%s%s] %3d%% (%d/%d)" "$bar" "$space" "$percent" "$current" "$total" } # 示例 - 处理文件列表并显示进度: # files=(*.txt); total=${#files[@]} # for ((i=0; i/dev/null; then local pid pid=$(cat "$lockfile/pid" 2>/dev/null) if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then echo "脚本已在运行 (PID: $pid)" >&2 return 1 fi # 僵尸锁,强制删除 rm -rf "$lockfile" mkdir "$lockfile" || return 1 fi echo "$$" > "$lockfile/pid" trap 'rm -rf "$lockfile"' EXIT return 0 } # 方法2: 使用 flock (Linux,更简洁) acquire_lock_flock() { local lockfile="${1:-$_lockfile}" exec 200>"$lockfile" if ! flock -n 200; then echo "脚本已在运行" >&2 return 1 fi # fd 200 会在脚本退出时自动释放锁 } # 脚本开头调用: # acquire_lock "/tmp/my-script.lock" || exit 1 ``` ###### 临时目录管理 ```bash # 创建临时目录并在脚本退出时自动清理 # 依赖: mktemp _tmpdirs=() tmpdir_create() { local dir dir=$(mktemp -d -t "${1:-tmp}.XXXXXX") _tmpdirs+=("$dir") echo "$dir" } _tmpdir_cleanup() { for dir in "${_tmpdirs[@]}"; do rm -rf "$dir" done } trap _tmpdir_cleanup EXIT # 简单版本(单个临时目录) tmpdir_simple() { local dir dir=$(mktemp -d -t "${1:-tmp}.XXXXXX") trap "rm -rf '$dir'" EXIT echo "$dir" } # 示例: # workdir=$(tmpdir_create "my-script") # echo "临时目录: $workdir" # cp /some/files "$workdir/" # # 脚本退出时自动清理 ``` ###### 命令依赖检查 ```bash # 检查必需的外部命令是否存在 # 依赖: 无 require() { local missing=() for cmd in "$@"; do if ! command -v "$cmd" &>/dev/null; then missing+=("$cmd") fi done if [[ ${#missing[@]} -gt 0 ]]; then echo -e "\e[0;31m[ERROR]\e[0m 缺少依赖命令: ${missing[*]}" >&2 echo "请使用包管理器安装后重试。" >&2 return 1 fi return 0 } # 检查指定版本 require_version() { local cmd="$1" required="$2" version if ! command -v "$cmd" &>/dev/null; then echo -e "\e[0;31m[ERROR]\e[0m 命令 $cmd 未安装" >&2 return 1 fi version=$("$cmd" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1) if [[ -z "$version" ]]; then echo -e "\e[0;33m[WARN]\e[0m 无法检测 $cmd 版本" >&2 return 0 fi echo "$cmd 版本: $version (需要 >= $required)" } # 示例: # require curl jq git || exit 1 # require_version git 2.0 ``` ###### 执行计时 ```bash # 测量命令执行耗时(高精度) # 依赖: Bash 4+ timer_start() { _timer_start=$(date +%s%N) } timer_stop() { local start="${1:-$_timer_start}" local end duration end=$(date +%s%N) duration=$(( (end - start) / 1000000 )) # 毫秒 if (( duration > 60000 )); then printf "耗时: %.2f 分钟\n" "$(bc -l <<<"$duration/60000")" elif (( duration > 1000 )); then printf "耗时: %.2f 秒\n" "$(bc -l <<<"$duration/1000")" else echo "耗时: ${duration}ms" fi } # 简单版本 - 包装命令执行 timed() { local start end start=$(date +%s%N) "$@" local ret=$? end=$(date +%s%N) local ms=$(( (end - start) / 1000000 )) echo "[timed] 退出码: $ret, 耗时: ${ms}ms" >&2 return $ret } # 示例: # timer_start # sleep 2 # timer_stop # # timed curl -s https://example.com ``` ###### JSON 快速提取 ```bash # 不依赖 jq 的 JSON 值提取(适用于简单 JSON) # 依赖: grep / awk # 提取字符串值 json_get_str() { local key="$1" json="${2:-$(cat)}" echo "$json" | grep -oP "\"${key}\"\s*:\s*\"[^\"]*\"" | \ grep -oP ':\s*"\K[^"]*' } # 提取数字值 json_get_num() { local key="$1" json="${2:-$(cat)}" echo "$json" | grep -oP "\"${key}\"\s*:\s*\d+(\.\d+)?" | \ grep -oP ':\s*\K\d+(\.\d+)?' } # 使用 awk 提取嵌套值 json_extract() { local key="$1" awk -v k="\"$key\"" '{ for (i=1; i<=NF; i++) { if ($i ~ k) { gsub(/[",]/, "", $(i+2)) print $(i+2) } } }' } # 示例: # json='{"name":"foo","age":30,"city":"beijing"}' # json_get_str "name" <<<"$json" # foo # json_get_num "age" <<<"$json" # 30 ``` ###### 路径规范化 ```bash # 将任何路径转为规范化后的绝对路径 # 依赖: readlink 或 realpath abspath() { local path="$1" # 方法1: 使用 realpath (如果可用) if command -v realpath &>/dev/null; then realpath -- "$path" return fi # 方法2: 使用 readlink -f (Linux) if readlink -f / &>/dev/null; then readlink -f -- "$path" return fi # 方法3: 纯 Bash 实现 local dir base if [[ -d "$path" ]]; then dir=$(cd "$path" && pwd -P) echo "$dir" elif [[ -f "$path" ]]; then base=$(basename "$path") dir=$(cd "$(dirname "$path")" && pwd -P) echo "$dir/$base" else echo "$path" fi } # 获取脚本所在目录(处理符号链接) script_dir() { local src="${BASH_SOURCE[0]}" while [[ -L "$src" ]]; do src=$(readlink "$src") done cd "$(dirname "$src")" && pwd -P } # 示例: # abspath "../relative/path" # SCRIPT_DIR=$(script_dir) ``` ###### 配置文件解析 ```bash # 解析 key=value (或 key = value) 格式的配置文件到关联数组 # 忽略注释和空行 # 依赖: Bash 4+ parse_config() { local config_file="$1" local -n _result="${2:-CONFIG}" # 引用目标关联数组 if [[ ! -f "$config_file" ]]; then echo "配置文件不存在: $config_file" >&2 return 1 fi declare -gA "$2" 2>/dev/null || declare -A _result while IFS='=' read -r key value; do # 跳过空行和注释 [[ -z "$key" || "$key" =~ ^[[:space:]]*# ]] && continue # 去除前后空白 key=$(echo "$key" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') value=$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') # 去除值中的引号 value="${value%\"}"; value="${value#\"}" value="${value%\'}"; value="${value#\'}" _result["$key"]="$value" done < <(grep -v '^\s*#' "$config_file" | grep -v '^\s*$') } # 示例: # declare -A cfg # parse_config /etc/myapp.conf cfg # echo "Host: ${cfg[host]}, Port: ${cfg[port]}" ``` ###### 数组工具 ```bash # 数组去重 # 依赖: 无 array_dedup() { local -n _in="$1" local -n _out="${2:-_in}" local -A _seen _out=() for item in "${_in[@]}"; do if [[ -z "${_seen[$item]:-}" ]]; then _out+=("$item") _seen["$item"]=1 fi done } # 数组包含检查 array_contains() { local item="$1" local -n _arr="$2" for e in "${_arr[@]}"; do [[ "$e" == "$item" ]] && return 0 done return 1 } # 数组最大值/最小值 (数字) array_max() { local -n _arr="$1" local max="${_arr[0]}" for v in "${_arr[@]:1}"; do (( v > max )) && max="$v" done echo "$max" } array_min() { local -n _arr="$1" local min="${_arr[0]}" for v in "${_arr[@]:1}"; do (( v < min )) && min="$v" done echo "$min" } # 数组乱序 array_shuffle() { local -n _arr="$1" local i j tmp for ((i=${#_arr[@]}-1; i>0; i--)); do j=$(( RANDOM % (i+1) )) tmp="${_arr[i]}"; _arr[i]="${_arr[j]}"; _arr[j]="$tmp" done } # 示例: # arr=(a b c a d b e) # array_dedup arr # arr 变成 (a b c d e) # array_contains "x" arr || echo "不存在" ``` ###### 批量重命名 ```bash # 安全批量重命名函数(支持预览模式) # 依赖: 无 batch_rename() { local pattern="$1" replacement="$2" dry_run="${3:-false}" local count=0 for file in *"$pattern"*; do # 跳过不存在的文件 [[ -e "$file" ]] || continue local newname="${file//$pattern/$replacement}" if [[ "$file" != "$newname" ]]; then if [[ "$dry_run" == "true" ]]; then echo -e "\e[0;34m[预览]\e[0m $file -> $newname" else mv -v -- "$file" "$newname" fi ((count++)) fi done echo "共处理 $count 个文件" >&2 } # 批量添加前缀/后缀 batch_prefix() { local prefix="$1" dry_run="${2:-false}" local count=0 for file in *; do [[ -f "$file" ]] || continue if [[ "$dry_run" == "true" ]]; then echo -e "\e[0;34m[预览]\e[0m $file -> ${prefix}${file}" else mv -v -- "$file" "${prefix}${file}" fi ((count++)) done echo "共处理 $count 个文件" >&2 } batch_suffix() { local suffix="$1" dry_run="${2:-false}" local count=0 name ext for file in *; do [[ -f "$file" ]] || continue name="${file%.*}" ext="${file##*.}" [[ "$file" == "$ext" ]] && ext="" || ext=".$ext" if [[ "$dry_run" == "true" ]]; then echo -e "\e[0;34m[预览]\e[0m $file -> ${name}${suffix}${ext}" else mv -v -- "$file" "${name}${suffix}${ext}" fi ((count++)) done echo "共处理 $count 个文件" >&2 } # 示例: # batch_rename " " "_" true # 预览: 空格替换为下划线 # batch_rename " " "_" false # 执行替换 # batch_prefix "backup-" true # 预览添加前缀 # batch_suffix "_v2" true # 预览添加后缀 ```