shell手册.md 79 KB

Shell 一行命令  [目录]

目录
工具: terminal - 终端
不退出重新加载 Shell
exec $SHELL -l
关闭 Shell 但保持所有子进程运行
disown -a && exit
退出不保存 Shell 历史
kill -9 $$
unset HISTFILE && exit
执行分支条件判断
true && echo success
false || echo failed
将 stdout 和 stderr 管道到不同的命令
some_command > >(/bin/cmd_for_stdout) 2> >(/bin/cmd_for_stderr)
将 stdout 和 stderr 分别重定向到不同文件,并同时打印到屏幕
(some_command 2>&1 1>&3 | tee errorlog ) 3>&1 1>&2 | tee stdoutlog
列出最常用的命令
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 历史
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。

快速备份文件
cp filename{,.orig}
清空文件(截断为 0 大小)
>filename
删除文件夹中不匹配特定文件扩展名的所有文件
rm !(*.foo|*.bar|*.baz)
将多行字符串传入文件
# cat  >filename ... - 覆写文件
# cat >>filename ... - 追加到文件
cat > filename << __EOF__
data data data
__EOF__
使用 vim 编辑远程主机上的文件
vim scp://user@host//etc/fstab
创建目录并同时进入
mkd() { mkdir -p "$@" && cd "$@"; }
将大写文件名转换为小写
rename 'y/A-Z/a-z/' *
在终端上打印一行字符
printf "%`tput cols`s" | tr ' ' '#'
显示 Shell 历史但不显示行号
history | cut -c 8-
fc -l -n 1 | sed 's/^\s*//'
退出会话后运行命令
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__
生成数字序列
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 文件监视
unset MAIL; export MAILCHECK=1; export MAILPATH='$FILE_TO_WATCH?$MESSAGE'

工具: busybox
静态 HTTP Web 服务器
busybox httpd -p $PORT -h $HOME [-c httpd.conf]

工具: mount - 挂载
挂载临时内存分区
mount -t tmpfs tmpfs /mnt -o size=64M
  • -t - 文件系统类型
  • -o - 挂载选项
将文件系统重新挂载为读写
mount -o remount,rw /

工具: fuser
显示哪些进程使用指定文件/目录
fuser /var/log/daemon.log
fuser -v /home/supervisor
杀死锁定文件的进程
fuser -ki filename
  • -i - 交互式选项
使用特定信号杀死锁定文件的进程
fuser -k -HUP filename
  • --list-signals - 列出可用信号名称
显示特定端口上监听的 PID
fuser -v 53/udp
显示所有使用指定文件系统或块设备的进程
fuser -mv /var/www

工具: lsof
显示当前使用互联网连接的进程
lsof -P -i -n
显示使用特定端口号的进程
lsof -i tcp:443
列出所有监听端口及关联进程的 PID
lsof -Pan -i tcp -i udp
列出所有开放端口及其所属可执行文件
lsof -i -P | grep -i "listen"
显示所有开放端口
lsof -Pnl -i
显示开放端口 (LISTEN)
lsof -Pni4 | grep LISTEN | column -t
列出特定命令打开的所有文件
lsof -c "process"
查看每个目录的用户活动
lsof -u username -a +D /etc
显示 10 个最大的打开文件
lsof / | \
awk '{ if($7 > 1048576) print $7/1048576 "MB" " " $9 " " $1 }' | \
sort -n -u | tail | column -t
显示进程的当前工作目录
lsof -p <PID> | grep cwd

工具: ps
显示可四向滚动的进程树及完整详情
ps awwfux | less -S
每个用户的进程数统计
ps hax -o user | sort | uniq -c | sort -r
按名称显示所有进程及主头部
ps -lfC nginx

工具: find - 查找
查找系统中过去 60 分钟内修改过的文件
find / -mmin 60 -type f
查找所有大于 20M 的文件
find / -type f -size +20M
查找重复文件(基于 MD5 哈希)
find -type f -exec md5sum '{}' ';' | sort | uniq --all-repeated=separate -w 33
仅更改文件权限
cd /var/www/site && find . -type f -exec chmod 766 {} \;
cd /var/www/site && find . -type f -exec chmod 664 {} +
仅更改目录权限
cd /var/www/site && find . -type d -exec chmod g+x {} \;
cd /var/www/site && find . -type d -exec chmod g+rwx {} +
查找特定用户/组的文件和目录
# 用户:
find . -user <username> -print
find /etc -type f -user <username> -name "*.conf"

# 组:
find /opt -group <group>
find /etc -type f -group <group> -iname "*.conf"
查找不属于特定用户/组的文件和目录
# 用户:
find . \! -user <username> -print

# 组:
find . \! -group <group>
查找仅具有特定权限的文件/目录
# 用户
find . -user <username> -perm -u+rw # -rw-r--r--
find /home -user $(whoami) -perm 777 # -rwxrwxrwx

# 组:
find /home -type d -group <group> -perm 755 # -rwxr-xr-x
删除 60 天前的文件
find . -type f -mtime +60 -delete
递归删除目录中所有空子目录
find . -depth  -type d  -empty -exec rmdir {} \;
查找文件的所有硬链接
find </path/to/dir> -xdev -samefile filename
递归查找最近修改的文件
find . -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head
使用 sed 递归查找/替换字符串
find . -not -path '*/\.git*' -type f -print0 | xargs -0 sed -i 's/foo/bar/g'
递归查找/替换目录和文件名中的字符串
find . -depth -name '*test*' -execdir bash -c 'mv -v "$1" "${1//foo/bar}"' _ {} \;
递归查找 suid 可执行文件
find / \( -perm -4000 -o -perm -2000 \) -type f -exec ls -la {} \;

工具: top
使用 top 仅监控包含特定字符串的进程
top -p $(pgrep -d , <str>)
  • <str> - 包含特定字符串的进程(如 nginx, worker)

工具: vmstat
显示当前系统利用率(字段以千字节为单位)
vmstat 2 20 -t -w
  • 2 - 定义时间间隔的次数(延迟)
  • 20 - 每次执行命令的次数(计数)
  • -t - 显示时间戳
  • -w - 宽输出
  • -S M - 以兆字节为单位输出字段
每 5 秒刷新显示当前系统利用率
vmstat 5 -w
显示磁盘操作摘要报告
vmstat -D
显示事件计数器和内存统计报告
vmstat -s
显示关于 slab 层缓存中内核对象的报告
vmstat -m
工具: iostat
显示 CPU 使用信息和所有分区的 I/O 统计
iostat 2 10 -t -m
  • 2 - 时间间隔(延迟)
  • 10 - 执行次数(计数)
  • -t - 显示时间戳
  • -m - 以兆字节为单位(-k 以千字节为单位,默认)
仅显示 CPU 利用率信息
iostat 2 10 -t -m -c
仅显示磁盘利用率信息
iostat 2 10 -t -m -d
仅显示 LVM 利用率信息
iostat -N

工具: strace
带子进程追踪
# 1)
strace -f -p $(pidof glusterfsd)

# 2)
strace -f $(pidof php-fpm | sed 's/\([0-9]*\)/\-p \1/g')
限制追踪进程 30 秒
timeout 30 strace $(< /var/run/zabbix/zabbix_agentd.pid)
追踪进程并将输出重定向到文件
ps auxw | grep '[a]pache' | awk '{print " -p " $2}' | \
xargs strace -o /tmp/strace-apache-proc.out
追踪并打印每个系统调用的耗时,限制打印字符串长度
ps auxw | grep '[i]init_policy' | awk '{print " -p " $2}' | \
xargs strace -f -e trace=network -T -s 10000
追踪网络端口的打开请求
strace -f -e trace=bind nc -l 80
追踪网络端口的打开请求(显示 TCP/UDP)
strace -f -e trace=network nc -lu 80

工具: kill
杀死在端口上运行的进程
kill -9 $(lsof -i :<port> | awk '{l=$2} END {print l}')

工具: diff
比较两个目录树
diff <(cd directory1 && find | sort) <(cd directory2 && find | sort)
比较两个命令的输出
diff <(cat /etc/passwd) <(cut -f2 /etc/passwd)

工具: vimdiff
基于字符和单词高亮精确差异
vimdiff file1 file2
比较两个 JSON 文件
vimdiff <(jq -S . A.json) <(jq -S . B.json)
比较十六进制转储
d(){ vimdiff <(f $1) <(f $2);};f(){ hexdump -C $1 | cut -d' ' -f3- | tr -s ' ';}; d ~/bin1 ~/bin2
diffchar

将 diffchar 保存到 ~/.vim/plugins

按 F7 在差异模式之间切换

有用的 vimdiff 命令:

  • qa 退出所有窗口
  • :vertical resize 70 调整窗口大小
  • 设置窗口宽度 Ctrl+W [N columns]+(Shift+)<\>

工具: tail
为 tail -f 添加时间戳注释
tail -f file | while read ; do echo "$(date +%T.%N) $REPLY" ; done
分析 Apache 访问日志中最常见的 IP 地址
tail -10000 access_log | awk '{print $1}' | sort | uniq -c | sort -n | tail
分析 Web 服务器日志并仅显示 5xx HTTP 状态码
tail -n 100 -f /path/to/logfile | grep "HTTP/[1-2].[0-1]\" [5]"

工具: tar
系统备份并排除特定目录
cd /
tar -czvpf /mnt/system$(date +%d%m%Y%s).tgz --directory=/ \
--exclude=proc/* --exclude=sys/* --exclude=dev/* --exclude=mnt/* .
系统备份并排除特定目录 (使用 pigz 并行压缩)
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
系统备份到文件
dump -y -u -f /backup/system$(date +%d%m%Y%s).lzo /
从 lzo 文件还原系统
cd /
restore -rf /backup/system$(date +%d%m%Y%s).lzo

工具: cpulimit
限制进程的 CPU 使用率
cpulimit -p pid -l 50

工具: pwdx
显示进程的当前工作目录
pwdx <pid>

工具: taskset
仅在单个 CPU 核心上启动命令
taskset -c 0 <command>

工具: tr
显示 PATH 中的目录,每行一个
tr : '\n' <<<$PATH

工具: chmod
从当前目录中的所有文件移除可执行位
chmod -R -x+X *
恢复 /bin/chmod 的权限
# 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
查找上次重启时间
who -b
检测用户是否通过 sudo su 进入当前 Shell
[[ $(who -m | awk '{ print $1 }') == $(whoami) ]] || echo "You are su-ed to $(whoami)"

工具: last
上次重启是意外崩溃吗?
(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
在分离模式下启动 screen
screen -d -m <command>
附加到现有的 screen 会话
screen -r -d <pid>

工具: script
录制和回放终端会话
### 录制会话
# 1)
script -t 2>~/session.time -a ~/session.log

# 2)
script --timing=session.time session.log

### 回放会话
scriptreplay --timing=session.time session.log

工具: du
显示 20 个最大的目录(以 K/M/G 标记)
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
每当目录中的文件被修改时触发操作
while true ; do inotifywait -r -e MODIFY dir/ && ls dir/ ; done;

工具: openssl
测试到远程主机的连接
echo | openssl s_client -connect google.com:443 -showcerts
测试到远程主机的连接(调试模式)
echo | openssl s_client -connect google.com:443 -showcerts -tlsextdebug -status
测试到远程主机的连接(支持 SNI)
echo | openssl s_client -showcerts -servername google.com -connect google.com:443
使用特定 SSL 版本测试到远程主机的连接
openssl s_client -tls1_2 -connect google.com:443
使用特定 SSL 密码测试到远程主机的连接
openssl s_client -cipher 'AES128-SHA' -connect google.com:443
验证 0-RTT
_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
生成无密码保护的私钥
# _len: 2048, 4096
( _fd="private.key" ; _len="2048" ; \
openssl genrsa -out ${_fd} ${_len} )
生成带密码保护的私钥
# _ciph: aes128, aes256
# _len: 2048, 4096
( _ciph="aes128" ; _fd="private.key" ; _len="2048" ; \
openssl genrsa -${_ciph} -out ${_fd} ${_len} )
移除私钥的密码保护
( _fd="private.key" ; _fd_unp="private_unp.key" ; \
openssl rsa -in ${_fd} -out ${_fd_unp} )
为现有私钥添加密码保护
# _ciph: aes128, aes256
( _ciph="aes128" ; _fd="private.key" ; _fd_pass="private_pass.key" ; \
openssl rsa -${_ciph} -in ${_fd} -out ${_fd_pass}
检查私钥
( _fd="private.key" ; \
openssl rsa -check -in ${_fd} )
从私钥获取公钥
( _fd="private.key" ; _fd_pub="public.key" ; \
openssl rsa -pubout -in ${_fd} -out ${_fd_pub} )
生成私钥和 CSR
( _fd="private.key" ; _fd_csr="request.csr" ; _len="2048" ; \
openssl req -out ${_fd_csr} -new -newkey rsa:${_len} -nodes -keyout ${_fd} )
生成 CSR
( _fd="private.key" ; _fd_csr="request.csr" ; \
openssl req -out ${_fd_csr} -new -key ${_fd} )
生成 CSR(从现有证书获取元数据)

其中 private.key 是现有的私钥。如你所见,不是从证书(公钥)生成此 CSR。也不是生成"相同"的 CSR,而是一个新的用于请求新证书的 CSR。

( _fd="private.key" ; _fd_csr="request.csr" ; _fd_crt="cert.crt" ; \
openssl x509 -x509toreq -in ${_fd_crt} -out ${_fd_csr} -signkey ${_fd} )
使用 -config 参数生成 CSR
( _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   = "<two-letter ISO abbreviation for your country>"
ST  = "<state or province where your organisation is legally located>"
L   = "<city where your organisation is legally located>"
O   = "<legal name of your organisation>"
OU  = "<section of the organisation>"
CN  = "<fully qualified domain name>"

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = <fully qualified domain name>
DNS.2 = <next domain>
DNS.3 = <next domain>
__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

完整示例:

( _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__
))

更多信息请参阅这些优秀的解释:

列出可用的 EC 曲线
openssl ecparam -list_curves
打印 ECDSA 私钥和公钥
( _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 私钥
# _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)
# _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 )
生成自签名证书
# _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} )
从现有私钥生成自签名证书
# _len: 2048, 4096
( _fd="domain.key" ; _fd_out="domain.crt" ; _days="365" ; \
openssl req -key ${_fd} -nodes \
-x509 -days ${_days} -out ${_fd_out} )
从现有私钥和 CSR 生成自签名证书
# _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 公共参数
( _dh_size="2048" ; \
openssl dhparam -out /etc/nginx/ssl/dhparam_${_dh_size}.pem "$_dh_size" )
显示 DH 公共参数
openssl pkeyparam -in dhparam.pem -text
从 pfx 中提取私钥
( _fd_pfx="cert.pfx" ; _fd_key="key.pem" ; \
openssl pkcs12 -in ${_fd_pfx} -nocerts -nodes -out ${_fd_key} )
从 pfx 中提取私钥和证书
( _fd_pfx="cert.pfx" ; _fd_pem="key_certs.pem" ; \
openssl pkcs12 -in ${_fd_pfx} -nodes -out ${_fd_pem} )
从 p7b 中提取证书
# 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
( _fd_der="cert.crt" ; _fd_pem="cert.pem" ; \
openssl x509 -in ${_fd_der} -inform der -outform pem -out ${_fd_pem} )
将 PEM 转换为 DER
( _fd_der="cert.crt" ; _fd_pem="cert.pem" ; \
openssl x509 -in ${_fd_pem} -outform der -out ${_fd_der} )
验证私钥
( _fd="private.key" ; \
openssl rsa -noout -text -in ${_fd} )
验证公钥
# 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 )
验证证书
( _fd="certificate.crt" ; # 格式: pem, cer, crt \
openssl x509 -noout -text -in ${_fd} )
验证 CSR
( _fd_csr="request.csr" ; \
openssl req -text -noout -in ${_fd_csr} )
检查私钥和证书是否匹配
(openssl rsa -noout -modulus -in private.key | openssl md5 ; \
openssl x509 -noout -modulus -in certificate.crt | openssl md5) | uniq
检查私钥和 CSR 是否匹配
(openssl rsa -noout -modulus -in private.key | openssl md5 ; \
openssl req -noout -modulus -in request.csr | openssl md5) | uniq

工具: secure-delete - 安全删除
使用 shred 安全删除
shred -vfuz -n 10 file
shred --verbose --random-source=/dev/urandom -n 1 /dev/sda
使用 scrub 安全删除
scrub -p dod /dev/sda
scrub -p dod -r file
使用 badblocks 安全删除
badblocks -s -w -t random -v /dev/sda
badblocks -c 10240 -s -w -t random -v /dev/sda
使用 secure-delete 安全删除
srm -vz /tmp/file
sfill -vz /local
sdmem -v
swapoff /dev/sda5 && sswap -vz /dev/sda5

工具: dd
让 dd 显示进度状态
dd <dd_params> status=progress
watch --interval 5 killall -USR1 dd
使用 dd 将输出重定向到文件
echo "string" | dd of=filename

工具: gpg
导出公钥
gpg --export --armor "<username>" > username.pkey
  • --export - 从所有密钥环导出所有密钥或特定密钥
  • -a|--armor - 创建 ASCII armored 输出
加密文件
gpg -e -r "<username>" dump.sql
  • -e|--encrypt - 加密数据
  • -r|--recipient - 为特定 加密
  • 解密文件
    gpg -o dump.sql -d dump.sql.gpg
    
    • -o|--output - 用作输出文件
    • -d|--decrypt - 解密数据(默认)
    搜索接收者
    gpg --keyserver hkp://keyserver.ubuntu.com --search-keys "<username>"
    
    • --keyserver - 设置特定密钥服务器
    • --search-keys - 在密钥服务器上搜索密钥
    列出加密文件中的所有数据包
    gpg --batch --list-packets archive.gpg
    gpg2 --batch --list-packets archive.gpg
    

    工具: system-other - 系统其他
    从 init 重启系统
    exec /sbin/init 6
    
    从单用户模式初始化系统
    exec /sbin/init
    
    显示进程的当前工作目录
    readlink -f /proc/<PID>/cwd
    
    显示已执行命令的实际路径名
    readlink -f /proc/<PID>/exe
    
    工具: curl
    curl -Iks https://www.google.com
    
    • -I - 仅显示响应头
    • -k - 使用 SSL 时的不安全连接
    • -s - 静默模式(不显示主体)

      curl -Iks --location -X GET -A "x-agent" https://www.google.com
      
    • --location - 跟随重定向

    • -X - 设置方法

    • -A - 设置 user-agent

      curl -Iks --location -X GET -A "x-agent" --proxy http://127.0.0.1:16379 https://www.google.com
      
    • --proxy [socks5://|http://] - 设置代理服务器

      curl -o file.pdf -C - https://example.com/Aiju2goo0Ja2.pdf
      
    • -o - 将输出写入文件

    • -C - 恢复传输

    查找你的外部 IP 地址(外部服务)
    curl ipinfo.io
    curl ipinfo.io/ip
    curl icanhazip.com
    curl ifconfig.me/ip ; echo
    
    重复 URL 请求
    # 使用虚拟查询字符串进行 URL 序列替换:
    curl -ks https://example.com/?[1-20]
    
    # 使用 Shell 'for' 循环:
    for i in {1..20} ; do curl -ks https://example.com/ ; done
    
    检查特定域名的 DNS 和 HTTP 跟踪及头部
    ### 设置域名和外部 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
    http -p Hh https://www.google.com
    
    • -p - 打印请求和响应头

      • H - 请求头
      • B - 请求体
      • h - 响应头
      • b - 响应体

        http -p Hh https://www.google.com --follow --verify no
        
    • -F, --follow - 跟随重定向

    • --verify no - 跳过 SSL 验证

      http -p Hh https://www.google.com --follow --verify no \
      --proxy http:http://127.0.0.1:16379
      
    • --proxy [http:] - 设置代理服务器

    工具: ssh
    转义序列
    # 支持的转义序列:
    ~.  - 终止连接(以及任何多路复用会话)
    ~B  - 向远程系统发送 BREAK
    ~C  - 打开命令行
    ~R  - 请求重新生成密钥(仅 SSH 协议 2)
    ~^Z - 挂起 ssh
    ~#  - 列出转发的连接
    ~&  - 后台 ssh(当等待连接终止时)
    ~?  - 此消息
    ~~  - 通过输入两次来发送转义字符本身
    
    比较远程文件与本地文件
    ssh user@host cat /path/to/remotefile | diff /path/to/localfile -
    
    通过中间主机进行 SSH 连接
    ssh -t reachable_host ssh unreachable_host
    
    通过 SSH 在远程主机上运行命令
    cat > cmd.txt << __EOF__
    cat /etc/hosts
    __EOF__
    
    ssh host -l user $(<cmd.txt)
    
    从私钥获取公钥
    ssh-keygen -y -f ~/.ssh/id_rsa
    
    获取所有指纹
    ssh-keygen -l -f .ssh/known_hosts
    
    使用用户密码进行 SSH 认证
    ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no user@remote_host
    
    使用公钥进行 SSH 认证
    ssh -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes -i id_rsa user@remote_host
    
    简单录制 SSH 会话
    function _ssh_sesslog() {
    
      _sesdir="<path/to/session/logs>"
    
      mkdir -p "${_sesdir}" && \
      ssh $@ 2>&1 | tee -a "${_sesdir}/$(date +%Y%m%d).log"
    
    }
    
    # 别名:
    alias ssh='_ssh_sesslog'
    
    使用 Keychain 进行 SSH 登录
    ### 删除 ssh-agent 的所有密钥。
    function _scl() {
    
      /usr/bin/keychain --clear
    
    }
    
    ### 添加密钥到 Keychain。
    function _scg() {
    
      /usr/bin/keychain /path/to/private-key
      source "$HOME/.keychain/$HOSTNAME-sh"
    
    }
    
    SSH 登录不处理任何登录脚本
    ssh -tt user@host bash
    
    SSH 本地端口转发

    示例 1:

    # 将本地 2250 端口通过 localhost 转发到 nmap.org:443
    host1> ssh -L 2250:nmap.org:443 localhost
    
    # 连接到服务:
    host1> curl -Iks --location -X GET https://localhost:2250
    

    示例 2:

    # 将本地 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 远程端口转发
    # 将本地 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
    测试到端口的远程连接
    timeout 1 bash -c "</dev/<proto>/<host>/<port>" >/dev/null 2>&1 ; echo $?
    
    • <proto - 设置协议 (tcp/udp)
    • <host> - 设置远程主机
    • <port> - 设置目标端口
    使用常用 Bash 工具读写 TCP 或 UDP 套接字
    exec 5<>/dev/tcp/<host>/<port>; cat <&5 & cat >&5; exec 5>&-
    

    工具: tcpdump
    过滤(接口上的)传入流量(特定 ip:port)
    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]
    过滤(接口上的)传入流量(特定 ip:port)并写入文件
    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 数据包
    tcpdump -nei eth0 icmp
    
    检查服务使用的协议(TCP 或 UDP)
    tcpdump -nei eth0 tcp port 22 -vv -X | egrep "TCP|UDP"
    
    显示 ASCII 文本(用于使用 grep 或其他工具解析输出)
    tcpdump -i eth0 -A -s0 port 443
    
    抓取两个关键字之间的所有内容
    tcpdump -i eth0 port 80 -X | sed -n -e '/username/,/=ldap/ p'
    
    抓取所有明文 HTTP 的用户名和密码
    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
    tcpdump -ei eth0 -nn -A -s1500 -l | grep "User-Agent:"
    
    仅捕获 HTTP GET 和 POST 数据包
    tcpdump -ei eth0 -s 0 -A -vv \
    'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' or 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354'
    

    或简单地:

    tcpdump -ei eth0 -s 0 -v -n -l | egrep -i "POST /|GET /|Host:"
    
    轮换捕获文件
    tcpdump -ei eth0 -w /tmp/capture-%H.pcap -G 3600 -C 200
    
    • -G <num> - 每 <num> 秒创建一个新的 pcap
    • -C <size> - 如果当前 pcap 大于 <size> 则关闭并打开新的
    按数据包数排名的顶部主机
    tcpdump -ei enp0s25 -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20
    
    排除任何 RFC 1918 私有地址
    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
    实时分析数据包
    while true ; do tcpick -a -C -r dump.pcap ; sleep 2 ; clear ; done
    

    工具: ngrep
    ngrep -d eth0 "www.domain.com" port 443
    
    • -d [iface|any] - 设置接口
    • [domain] - 设置主机名
    • port [1-65535] - 设置端口号

      ngrep -d eth0 "www.domain.com" src host 10.240.20.2 and port 443
      
    • (host [ip|hostname]) - 按 IP 或主机名过滤

    • (port [1-65535]) - 按端口号过滤

      ngrep -d eth0 -qt -O ngrep.pcap "www.domain.com" port 443
      
    • -q - 安静模式(仅有效载荷)

    • -t - 添加时间戳

    • -O [filename] - 将输出保存到文件,-I [filename] - 从文件读取

      ngrep -d eth0 -qt 'HTTP' 'tcp'
      
    • HTTP - 显示 HTTP 头部

    • tcp|udp - 设置协议

    • [src|dst] host [ip|hostname] - 设置特定节点的方向

      ngrep -l -q -d eth0 -i "User-Agent: curl*"
      
    • -l - stdout 行缓冲

    • -i - 不区分大小写搜索


    工具: hping3
    hping3 -V -p 80 -s 5050 <scan_type> www.google.com
    
    • -V|--verbose - 详细模式
    • -p|--destport - 设置目标端口
    • -s|--baseport - 设置源端口
    • <scan_type> - 设置扫描类型

      • -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),端口开放则无回复

        hping3 -V -c 1 -1 -C 8 www.google.com
        
    • -c [num] - 数据包计数

    • -1 - 设置 ICMP 模式

    • -C|--icmptype [icmp-num] - 设置 icmp 类型(默认 icmp-echo = 8)

      hping3 -V -c 1000000 -d 120 -S -w 64 -p 80 --flood --rand-source <remote_host>
      
    • --flood - 尽可能快地发送数据包(不显示回复)

    • --rand-source - 随机源地址模式

    • -d --data - 数据大小

    • -w|--win - winsize(默认 64)


    工具: nmap
    Ping 扫描网络
    nmap -sP 192.168.0.0/24
    
    仅显示开放端口
    nmap -F --open 192.168.0.0/24
    
    全 TCP 端口扫描及服务版本检测
    nmap -p 1-65535 -sV -sS -T4 192.168.0.0/24
    
    Nmap 扫描并将输出传递给 Nikto
    nmap -p80,443 192.168.0.0/24 -oG - | nikto.pl -h -
    
    使用 Nmap NSE 脚本堆栈侦察特定 ip:service
    # 设置变量:
    _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
    nc -kl 5000
    
    • -l - 监听传入连接
    • -k - 客户端断开后继续监听
    • >filename.out - 将接收到的数据保存到文件(可选)

      nc 192.168.0.1 5051 < filename.in
      
    • < filename.in - 发送数据到远程主机

      nc -vz 10.240.30.3 5000
      
    • -v - 详细输出

    • -z - 扫描监听守护进程

      nc -vzu 10.240.30.3 1-65535
      
    • -u - 仅扫描 udp 端口

    传输数据文件(归档)
    server> nc -l 5000 | tar xzvfp -
    client> tar czvfp - /path/to/dir | nc 10.240.30.3 5000
    
    启动远程 Shell
    # 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
    
    简单文件服务器
    while true ; do nc -l 5000 | tar -xvf - ; done
    
    简单的极简 HTTP 服务器
    while true ; do nc -l -p 1500 -c 'echo -e "HTTP/1.1 200 OK\n\n $(date)"' ; done
    
    简单 HTTP 服务器

    每次请求后重启 Web 服务器 - 移除 while 条件则仅允许单次连接。

    cat > index.html << __EOF__
    <!doctype html>
        <head>
            <meta charset="utf-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
            <title></title>
            <meta name="description" content="">
            <meta name="viewport" content="width=device-width, initial-scale=1">
        </head>
        <body>
    
        <p>
    
          Hello! It's a site.
    
        </p>
    
        </body>
    </html>
    __EOF__
    
    server> while : ; do \
    (echo -ne "HTTP/1.1 200 OK\r\nContent-Length: $(wc -c <index.html)\r\n\r\n" ; cat index.html;) | \
    nc -l -p 5000 \
    ; done
    
    • -p - 端口号
    简单 HTTP 代理(单连接)
    #!/usr/bin/env bash
    
    if [[ $# != 2 ]] ; then
      printf "%s\\n" \
             "usage: ./nc-proxy listen-port bk_host:bk_port"
    fi
    
    _listen_port="$1"
    _bk_host=$(echo "$2" | cut -d ":" -f1)
    _bk_port=$(echo "$2" | cut -d ":" -f2)
    
    printf "  lport: %s\\nbk_host: %s\\nbk_port: %s\\n\\n" \
           "$_listen_port" "$_bk_host" "$_bk_port"
    
    _tmp=$(mktemp -d)
    _back="$_tmp/pipe.back"
    _sent="$_tmp/pipe.sent"
    _recv="$_tmp/pipe.recv"
    
    trap 'rm -rf "$_tmp"' EXIT
    
    mkfifo -m 0600 "$_back" "$_sent" "$_recv"
    
    sed "s/^/=> /" <"$_sent" &
    sed "s/^/<=  /" <"$_recv" &
    
    nc -l -p "$_listen_port" <"$_back" | \
    tee "$_sent" | \
    nc "$_bk_host" "$_bk_port" | \
    tee "$_recv" >"$_back"
    
    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 代理
    ### 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
    测试到远程主机的连接(支持 SNI)
    gnutls-cli -p 443 google.com
    
    测试到远程主机的连接(不支持 SNI)
    gnutls-cli --disable-sni -p 443 google.com
    

    工具: socat
    测试到端口的远程连接
    socat - TCP4:10.240.30.3:22
    
    • - - 标准输入 (STDIO)
    • TCP4:<params> - 设置具有特定参数的 tcp4 连接
      • [hostname|ip] - 设置主机名/ip
      • [1-65535] - 设置端口号
    在 Linux 下将 TCP 流量重定向到 UNIX 域套接字
    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:<params> - 设置具有特定参数的 tcp 监听
      • [1-65535] - 设置端口号
      • bind=[hostname|ip] - 设置绑定的主机名/ip
      • reuseaddr - 允许其他套接字绑定到某个地址
      • fork - 保持父进程尝试产生更多连接
      • su=nobody - 设置用户
      • range=[ip-range] - IP 范围
    • UNIX-CLIENT:<params> - 与指定的对等套接字通信
      • filename - 定义套接字

    工具: p0f
    将接口设置为混杂模式并将流量转储到日志文件
    p0f -i enp0s25 -p -d -o /dump/enp0s25.log
    
    • -i - 监听指定接口
    • -p - 将接口设置为混杂模式
    • -d - fork 到后台
    • -o - 输出文件

    工具: netstat
    为每个主机的连接数绘制图形
    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 排序
    watch "netstat -plan | grep :443 | awk {'print \$5'} | cut -d: -f 1 | sort | uniq -c | sort -nk 1"
    
    从本地 IPv4 监听端口抓取横幅
    netstat -nlt | grep 'tcp ' | grep -Eo "[1-9][0-9]*" | xargs -I {} sh -c "echo "" | nc -v -n -w1 127.0.0.1 {}"
    

    工具: rsync
    使用 sudo 作为 root 远程同步数据
    rsync --rsync-path 'sudo rsync' username@hostname:/path/to/dir/ /local/
    

    工具: host
    解析域名(使用外部 DNS 服务器)
    host google.com 9.9.9.9
    
    检查域管理员(SOA 记录)
    host -t soa google.com 9.9.9.9
    

    工具: dig
    解析域名(简短输出)
    dig google.com +short
    
    查询特定域的 NS 记录
    dig @9.9.9.9 google.com NS
    
    仅查询答案部分
    dig google.com +nocomments +noquestion +noauthority +noadditional +nostats
    
    查询所有 DNS 记录
    dig google.com ANY +noall +answer
    
    DNS 反向查询
    dig -x 172.217.16.14 +short
    

    工具: certbot
    生成多域名证书
    certbot certonly -d example.com -d www.example.com
    
    生成通配符证书
    certbot certonly --manual --preferred-challenges=dns -d example.com -d *.example.com
    
    使用 4096 位私钥生成证书
    certbot certonly -d example.com -d www.example.com --rsa-key-size 4096
    

    工具: network-other - 网络其他
    获取特定 AS(自治系统)的所有子网
    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 解析域名
    _dname="google.com" ; curl -s "https://dns.google.com/resolve?name=${_dname}&type=A" | jq .
    
    工具: git
    用于体面查看仓库的日志别名
    # 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
    静态 HTTP Web 服务器
    # Python 3.x
    python3 -m http.server 8000 --bind 127.0.0.1
    
    # Python 2.x
    python -m SimpleHTTPServer 8000
    
    带 SSL 支持的静态 HTTP Web 服务器
    # 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
    python -m base64 -e <<< "sample string"
    
    解码 Base64
    python -m base64 -d <<< "dGhpcyBpcyBlbmNvZGVkCg=="
    
    工具: awk
    搜索匹配的行
    # egrep foo
    awk '/foo/' filename
    
    搜索不匹配的行
    # egrep -v foo
    awk '!/foo/' filename
    
    打印带行号的匹配行
    # egrep -n foo
    awk '/foo/{print FNR,$0}' filename
    
    打印最后一列
    awk '{print $NF}' filename
    
    查找所有超过 80 个字符的行
    awk 'length($0)>80{print FNR,$0}' filename
    
    仅打印少于 80 个字符的行
    awk 'length < 80' filename
    
    在文件中打印双倍空行
    awk '1; { print "" }' filename
    
    打印行号
    awk '{ print FNR "\t" $0 }' filename
    awk '{ printf("%5d : %s\n", NR, $0) }' filename   # 以更漂亮的方式
    
    仅为非空行打印行号
    awk 'NF { $0=++a " :" $0 }; { print }' filename
    
    打印匹配正则表达式的行及其后两行(i=5)
    awk '/foo/{i=5+1;}{if(i){i--; print;}}' filename
    
    打印从匹配 'server {' 开始到匹配 '}' 结束的行
    awk '/server {/,/}/' filename
    
    打印带分隔符的多列
    awk -F' ' '{print "ip:\t" $2 "\n port:\t" $3' filename
    
    移除空行
    awk 'NF > 0' filename
    
    # 替代方案:
    awk NF filename
    
    删除尾部空白(空格、制表符)
    awk '{sub(/[ \t]*$/, "");print}' filename
    
    删除前导空白
    awk '{sub(/^[ \t]+/, ""); print}' filename
    
    移除连续重复行
    # uniq
    awk 'a !~ $0{print}; {a=$0}' filename
    
    在不排序的情况下移除文件中的重复条目
    awk '!x[$0]++' filename
    
    排除多列
    awk '{$1=$3=""}1' filename
    
    对于匹配正则表达式的行,将 foo 替换为 bar
    awk '/regexp/{gsub(/foo/, "bar")};{print}' filename
    
    在匹配行的开头添加一些字符
    awk '/regexp/{sub(/^/, "++++"); print;next;}{print}' filename
    
    获取 Apache 日志的最后一小时
    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
    打印文件中的特定行
    sed -n 10p /path/to/file
    
    从文件中删除特定行
    sed -i 10d /path/to/file
    # 替代方案 (BSD): sed -i'' 10d /path/to/file
    
    从文件中删除连续范围的行
    sed -i <file> -re '<start>,<end>d'
    
    将换行符替换为空格
    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 全局匹配(尽可能多次)

    替代方案:

    # perl 版本(类似 sed 的速度):
    perl -p -e 's/\n/ /' /path/to/file
    
    # bash 版本(较慢):
    while read line ; do printf "%s" "$line " ; done < file
    
    删除字符串及其后 +N 行
    sed '/start/,+4d' /path/to/file
    

    工具: grep
    在当前目录所有文件中搜索 "pattern"
    grep -rn "pattern"
    grep -RnisI "pattern" *
    fgrep "pattern" * -R
    
    仅显示匹配多个模式的结果
    grep 'INFO*'\''WARN' filename
    grep 'INFO\|WARN' filename
    grep -e INFO -e WARN filename
    grep -E '(INFO|WARN)' filename
    egrep "INFO|WARN" filename
    
    排除多个模式
    grep -vE '(error|critical|warning)' filename
    
    显示文件中没有注释的数据
    grep -v ^[[:space:]]*# filename
    
    显示文件中没有注释和空行的数据
    egrep -v '#|^$' filename
    
    显示包含连字符/短划线的字符串
    grep -e -- filename
    grep -- -- filename
    grep "\-\-" filename
    
    从文件中删除空行并将输出保存到新文件
    grep . filename > newfilename
    
    工具: perl
    搜索和替换(原地编辑)
    perl -i -pe's/SEARCH/REPLACE/' filename
    
    编辑 *.conf 文件将所有 foo 改为 bar(并备份原始文件)
    perl -p -i.orig -e 's/\bfoo\b/bar/g' *.conf
    
    打印 *.conf 文件的前 20 行
    perl -pe 'exit if $. > 20' *.conf
    
    搜索第 10 到 20 行
    perl -ne 'print if 10 .. 20' filename
    
    删除前 10 行(并备份原始文件)
    perl -i.orig -ne 'print unless 1 .. 10' filename
    
    删除 foo 和 bar 之间行之外的所有内容(并备份原始文件)
    perl -i.orig -ne 'print unless /^foo$/ .. /^bar$/' filename
    
    将多个连续空行缩减为单行
    perl -p -i -00pe0 filename
    
    将制表符转换为空格(1t = 2sp)
    perl -p -i -e 's/\t/  /g' filename
    
    从文件读取输入并报告行数和字符数
    perl -lne '$i++; $in += length($_); END { print "$i lines, $in characters"; }' filename
    

    Shell 技巧  [目录]

    目录
    获取干净 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)
    !!                  # 重复上一条命令
    sudo !!             # 用 sudo 重新执行上一条命令
    !$                  # 上一条命令的最后一个参数
    !^                  # 上一条命令的第一个参数
    !*                  # 上一条命令的所有参数
    !:n                 # 上一条命令的第 n 个参数 (0-indexed,命令本身为 :0)
    !:-                 # 上一条命令的所有参数(不含命令名)
    !<n>                # 执行历史中第 n 条命令
    !<string>           # 执行最近以 string 开头的命令
    ^old^new            # 将上一条命令中的 old 替换为 new 并执行
    !:gs/old/new        # 将上一条命令中所有 old 替换为 new 并执行
    
    花括号展开 (Brace Expansion)
    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)
    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 变量的值(间接展开)
    
    目录导航
    # 目录栈操作
    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"; }
    
    重定向进阶
    # 进程替换 - 将命令输出当作文件使用
    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 信号捕获
    # 退出时清理临时文件
    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 选项
    # 编写健壮脚本的必备选项
    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 -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
    
    后台任务管理
    # 后台运行
    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 颜色和光标控制
    # 颜色定义
    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
    # 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 <<EOF
    当前用户: $(whoami),时间: $(date)
    EOF
    
    # 前置制表符可以加 <<- 自动去除缩进(仅限制表符,非空格)
    cat <<-EOF
    	缩进的内容
    	输出时会去掉前导制表符
    EOF
    
    特殊变量速查
    $?       # 上一条命令的退出状态码 (0=成功, 非0=失败)
    $!       # 最近后台命令的 PID
    $$       # 当前 shell 的 PID
    $_       # 上一条命令的最后一个参数(或 shell 启动时的绝对路径)
    $-       # 当前 shell 的选项标志 (如 himBHs)
    $0       # 脚本名称或 shell 名称
    $1-$9    # 位置参数 1-9
    ${10}    # 第 10 个及以上位置参数必须用大括号
    $#       # 位置参数的个数
    $@       # 所有位置参数(每个参数作为独立单词,用引号时保持原样: "$@")
    $*       # 所有位置参数(作为单个字符串,受 IFS 影响: "$*")
    $IFS     # 内部字段分隔符 (默认: 空格 制表符 换行符)
    $LINENO  # 当前脚本行号
    $RANDOM  # 0-32767 之间的随机整数
    $SECONDS # 当前 shell 已运行的秒数
    $PIPESTATUS # 最近管道中所有命令的退出状态数组
    
    # $@ vs $* 的区别:
    func() { echo "参数个数: $#"; }
    args="a b c"
    func "$args"    # 1 个参数: "a b c"
    func $args      # 3 个参数: "a" "b" "c"
    
    #### Shell 函数 &nbsp;[<sup>[目录]</sup>](#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<total; i++)); do

    process_file "${files[i]}"

    progress_bar $((i+1)) "$total"

    done

    echo

    
    ###### URL 编解码
    
    

    bash

    URL 编码 (纯 Bash 4+)

    依赖: Bash 4.x

    urlencode() { local string="${1:-$(cat)}" local encoded="" local c

    for ((i=0; i<${#string}; i++)); do

    c="${string:i:1}"
    case "$c" in
      [a-zA-Z0-9.~_-]) encoded+="$c" ;;
      *) printf -v hex '%%%02X' "'$c"
         encoded+="$hex" ;;
    esac
    

    done

    echo "$encoded" }

    URL 解码 (使用 awk)

    依赖: awk

    urldecode() { awk -v RS='+' '{

    gsub(/%[0-9a-fA-F][0-9a-fA-F]/, "&\n")
    printf "%s", $0
    

    }' <<<"${1:-$(cat)}" | \ awk '{

    if (match($0, /%[0-9a-fA-F][0-9a-fA-F]/))
      printf "%c", strtonum("0x" substr($0, RSTART+1, RLENGTH-1))
    else
      printf "%s", $0
    

    }' }

    示例:

    urlencode "hello world" # hello%20world

    urldecode "hello%20world" # hello world

    
    ###### 随机密码生成
    
    

    bash

    生成随机密码/字符串

    依赖: /dev/urandom

    genpass() { local length="${1:-16}" local charset="${2:-A-Za-z0-9}"

    # 使用 /dev/urandom LC_ALL=C tr -dc "$charset" </dev/urandom | head -c "$length" echo }

    不同类型密码的字符集:

    仅字母数字: A-Za-z0-9

    含特殊字符: A-Za-z0-9!@#$%^&*

    仅十六进制: 0-9A-F

    仅可打印: [:print:]

    备选: 使用 openssl

    genpass_openssl() { openssl rand -base64 "${1:-16}" | tr -d '=+/' | head -c "${1:-16}" echo }

    示例:

    genpass # 默认 16 位字母数字

    genpass 32 # 32 位

    genpass 20 "A-Za-z0-9!@#$%" # 20 位含特殊字符

    
    ###### 锁文件机制
    
    

    bash

    防止脚本重复执行(基于 flock 或 mkdir)

    依赖: 无

    _lockfile="/var/run/$(basename "$0").lock"

    acquire_lock() { local lockfile="${1:-$_lockfile}"

    # 方法1: 使用 mkdir(原子操作,最可移植) if ! mkdir "$lockfile" 2>/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 }

    示例:

    batchrename " " "" true # 预览: 空格替换为下划线

    batchrename " " "" false # 执行替换

    batch_prefix "backup-" true # 预览添加前缀

    batch_suffix "_v2" true # 预览添加后缀

    ```