Skip to main content

Bash

# empty shell
env -i bash

# empty env
env -i HOME="$HOME" bash -c 'env'

# login shell
time env -i HOME="$HOME" LOG4BASH_LOG_LEVE=DEBUG /usr/local/bin/bash -l -c 'env'
flagfor
-c COMMANDcommand string
-iinteractive
-llogin shell
-rrestricted
-sstdin
# 传递 flag 给 stdin 脚本
curl http://sh | bash -s -- -flag

语法

  • [ = test
  • [[ - shell 内置语法 - 速度更快一点点
    • 支持 &&, || 语法
  • {} - 展开
    • ${var}
    • ${var:=default}
    • ${var/find/replace}
    • ${var%remove}
    • {000..2} - 序列展开
    • {000..2..2} - 带 step 参数
    • echo x{,A,B,C} - 组合展开
    • { echo 1; echo 2; } - 命令序列
  • (()) - 算术操作
  • () - subshell, array
# for 循环
# ===========
# bash 语法
for ((i = 0; i < 3; i++)); do echo $i; done
# bash 展开序列
for i in {0..2}; do echo $i; done
# 使用 seq 生成序列 - ash 支持
for i in $(seq 0 2); do echo $i; done
for i in $(seq 0 $((3 - 1))); do echo $i; done

set

# 推荐
set -Eeuo pipefail

shopt

  • Bash 扩展选项
shopt [-pqsu] [-o] [optname …]
shopt -s extglob # 开启
shopt -u extglob # 关闭
shopt -q extglob # 使用 exit code 表示是否开启
shopt extglob # 当前状态
shopt # 全部状态
shopt -o nolog # 限定内置 set 支持选项而非扩展选项

# 所有 on 的选项
shopt | grep "on$" | grep -o '^\S\+'
# 推荐
shopt -s autocd cdspell extglob globstar

# 默认开启选项
shopt -s checkwinsize cmdhist expand_aliases extquote force_fignore hostcomplete interactive_comments progcomp promptvars sourcepath
  • globstar - ** 会匹配当前而不只是子目录
    # 会匹配 Makefile
    ls **/Makefile
  • extglob - 扩展 glob 语法
    # 复制 scripts 下的 Makefile 到所有其他 Makefile
    ls !(scripts)/**/Makefile | xargs -n 1 cp scripts/stub/Makefile
  • nocaseglob - glob 大小写不敏感
  • histappend - 追加到历史文件而不是重写
  • cdspell - 使用 cd 时自动校正书写
    cd ignorad # 会 cd 到存在的 ignored 目录 - a -> e
  • autocd - 输入目录自动切换到目录
  • failglob - glob 匹配不到文件时出错而非 直接输出相同字符

环境变量

envdefaultdescription
EPOCHSECONDS
EPOCHREALTIME
BASH_ARGV0
echo $EPOCHSECONDS

.inputrc

C-x C-r is bound to re-read-init-file.

https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File.html

https://www.gnu.org/software/bash/manual/bashref.html#Miscellaneous-Commands

# Make Tab autocomplete regardless of filename case
set completion-ignore-case on

# List all matches in case multiple possible completions are possible
set show-all-if-ambiguous on

# Immediately add a trailing slash when autocompleting symlinks to directories
set mark-symlinked-directories on

# Use the text that has already been typed as the prefix for searching through
# commands (i.e. more intelligent Up/Down behavior)
"\eOA": history-search-backward
"\e[A": history-search-backward
"\eOB": history-search-forward
"\e[B": history-search-forward

FAQ

Prefix/Suffix

# 去除前缀和后缀
x="/foo/fizzbuzz.bar"
y=${x%.bar}
echo ${y##*/}
#> fizzbuzz

# 去除后缀
x="/foo/fizzbuzz.bar.quux"
y=${x%.*}
echo $y
#> /foo/fizzbuzz.bar
y=${x%%.*}
echo $y
#> /foo/fizzbuzz

# 去除前缀
x=/foo/bar/bim/baz/file.gif
y=${path##*/}
echo $y
#> file.gif

替换

a=abc/da
# zbc/da
echo ${a/a/z}
# // 是所有
# zbc/dz
echo ${a//a/z}
# 转意
# abczda
echo ${a//\//z}

Parallels

# do it once
seq 1 | parallel -n0 "curl -H 'Content-Type: application/json' http://httpbin.org/post -X POST -d '{\"url\":\"http://google.com/\"}'"

# do it twice
seq 2 | parallel -n0 "curl -H 'Content-Type: application/json' http://httpbin.org/post -X POST -d '{\"url\":\"http://google.com/\"}'"

# do it 4 times, but at 2 a time
seq 4 | parallel -n0 -j2 "curl -H 'Content-Type: application/json' http://httpbin.org/post -X POST -d '{\"url\":\"http://google.com/\"}'"

# you can also put all your commands into a file
echo "ls\nls" > somefile
cat somefile | parallel

# finally, just straight command line parameters
parallel echo ::: a b c

# by default, it runs as many jobs in parallel as there are cores in your computer

# if you try and set more jobs than there are cores, they will be concurrent, but
# they will only ever be parallel up to your core count

# for httpie use the --ignore-stdin flag, or else it gets mixed up
seq 1 | parallel -n0 "http --ignore-stdin POST http://httpbin.org/post url=http://google.com/"

Base N

# Encode base62
BASE62=($(echo {0..9} {a..z} {A..Z}))
for i in $(bc <<< "obase=62; 9207903953"); do
echo -n ${BASE62[$(( 10#$i ))]}
done && echo
# Decode base62
base62_decode() { echo $((62#$1)) }

SSH

# download: remote -> local
# local_file 可以为目录用 -r 递归
scp user@remote_host:remote_file local_file
# upload: local -> remote
scp local_file user@remote_host:remote_file

# To Forward sshtalk.in:8080 -> Cort.local:4567, you can do
local="Cort.local:4567" # or "localhost:4567"
remot="*:8080" # "*" for all interfaces (default is loopback)

ssh -R ${remote}:${local} sshtalk.in

# To forward localhost:1234 -> private-host:443, through public-host you can do
local="localhost:1234" # or just "1234" default is localhost
remot="private-host:443" # "*" for all interfaces (default is loopback)

ssh -L ${local}:${remote} public-host

# 须在在Server端允许转发
# 在 /etc/ssh/sshd_config 中添加
# GatewayPorts yes
# 然后重启
sudo service sshd restart
# scp to 多台
for dest in $(< destfile.txt); do
scp ourfile.txt ${dest}:remote/path/
done

# 在不用 scp 的情况下 拷贝到多台
cat file.txt | tee \
>(ssh user@ip1.com "cat > file.txt") \
>(ssh user@ip2.com "cat > file.txt")

tar cz file1 file2 file3 | tee \
>(ssh user@ip1.com "tar xz") \
>(...)

xargs

case

case $space in
[1-6]*)
Message="All is quiet."
;;
[7-8]*)
Message="Start thinking about cleaning out some stuff. There's a partition that is $space % full."
;;
9[1-8])
Message="Better hurry with that new disk... One partition is $space % full."
;;
99)
Message="I'm drowning here! There's a partition at $space %!"
;;
*)
Message="I seem to be running with an nonexistent amount of disk space..."
;;
esac

使用变量名字访问变量

a=PATH
echo ${!a}

dotenv

env $(cat .env | xargs) sh -c 'echo $MY_ENV'

heredoc

cat << EOF
PWD=$PWD
EOF
# 转义
cat << "EOF"
PWD=$PWD
EOF
# 移除缩进 - 需要 tab
cat <<- EOF
PWD=$PWD
EOF

sub shell

(echo 1)

# 会输出 1 2 3 4
bash << SH &
echo 1
echo 2 && (echo 3; exit 1;)
echo 4
SH

tcp redir

echo <> /dev/tcp/wener.me/80
# 0 - 连通
echo $?

echo <> /dev/tcp/localhost/80
# 1
echo $?

git branch

curl -Lo ~/.git-prompt.sh https://github.com/git/git/raw/master/contrib/completion/git-prompt.sh
source ~/.git-prompt.sh

__git_ps1 '%s'

export PS1='\[\e[32m\]\u@\h \[\e[01;33m\]\w $(__git_ps1 " (%s)") \[\e[34m\][\t] \[\e[0m\]\n$ '