Linux Shell 快速入门笔记
Linux Shell 快速入门笔记
# 前言
随着工作经历的增加,我在 .zshrc
里东拼西凑的 alias
和 function
越堆越多。对于轻度代码洁癖患者来说,看着这么多乱七八糟却又一知半解的代码片段实在是有点难受。无论是拆分整理还是重构,是时候系统学习一下 shell 了。
之前有几次想学没学成,原因还是方法不对,直接试图去啃 ZSH Documentation (opens new window),这谁顶得住啊。
由于已经有一丢丢使用经验(mkdir
, cd
啥的平时都要用的),所以这次主要是查漏补缺一些符号的含义(比如 2>&1
, $()
, [[]]
, $#
)和一些 scripting 上的概念(source
, export
, local
)。
通过《The Linux Command Line》中英两版 PDF 加上一些视频,总计两三天花了大约 16h。有了较为完整的概念,初步实现能读能写(一些语法细节还需要随用随查),满足了日常需要,对于速刷入门来说效果还不错。(也顺势重构了一些以前写的脚本)
# 教材
- 视频
- 书
- 补充文档
- 内建变量
- 内建命令
- 参数展开
- 条件表达式 Conditional Expressions
- ANSI escape code (opens new window)
- 重定向
- 实用工具
# Shell 知识体系
- (结构和功能)
- Linux 文件系统层次标准(基础目录结构)
/
,/bin
,/usr/bin
,/dev
,/tmp
,/etc
,/opt
- 一些内建命令 Builtin Commands
command -options arguments
echo
,exit
- 路径:
ls
,cd
,pwd
- 文件:
file
,less
,cat
cp
,mv
,mkdir
,rm
,ln
locate
,find
,touch
,stat
- 使用命令:
type
,which
,man
,info
,whatis
,help
,--help
,alias
- 权限:
id
,chmod
,chown
,chgrp
,umask
,su
,sudo
,passwd
- 进程:
ps
,top
,jobs
,bg
,fg
,kill
,killall
,shutdown
- Shell 环境:
printenv
,set
,export
,source
- 文本编辑:
vi
,nano
- Misc:
trap
,wait
,sleep
- 一些内建变量 Shell Varibles
$$
,$?
,$_
$PWD
,$SHELL
,$USER
,$HOME
,$HOSTNAME
$PATH
:环境变量$PS1
:提示符 Prompt$PS4
:追踪调试set -x
,set +x
$RANDOM
- (更多内建命令)
- 存储:
mount
,unmount
(略) - 网络:
ping
,traceroute
,wget
,ssh
- 归档压缩:
gzip
,tar
,rsync
- 正则表达式:
grep
- 文本处理:
cat
,sort
,uniq
,diff
,sed
- 格式化输出:
fmt
,printf
- 打印机:(略)
- 编译:
make
- 存储:
- Linux 文件系统层次标准(基础目录结构)
- 键盘操作
- less、man 等控制:
h
,?
,q
,/pattern
,n
,N
- 命令行光标:
Ctrl-u
,Ctrl-y
- less、man 等控制:
- (命令行书写)
- 路径
- 相对路径:
./
,../
- 绝对路径:
/usr/bin/env
cd -
,cd ~username
- 通配符 Wildcards:
*.txt
,Data???
,[abc]
,[!abc]
,[![:upper:]]
- 相对路径:
- 展开 Expansion
- 路径展开:
echo *.txt
,ls ~username
- 算数表达式展开 Arithmetic Expansion:
echo $(( (2+3)*4 ))
- 花括号展开 Brace Expansion:
echo file_{A{1,2},B{1,2}}
,echo {0..z}
- 参数展开 Parameter Expansion:
echo $USER
,echo $PWD
,echo ${USER}
- 值检查和替换:
echo ${var:-fallbackVal}
,echo ${var:=defaultVal}
,echo ${var:+successVal}
- 值检查和报错:
echo ${var:?errMsg}
- 子串:
echo ${parameter:offset:length}
- 替换:
echo ${parameter/pattern/replacer}
- 长度:
${#foo}
- (更多展开和处理
${}
略)
- 命令替换 Command Substitution:
ls -l $(which env)
- 引号,转义 Escape:
echo "Hello $USER"
,echo '$USER'
,echo "\$5.00"
- history:
!!
,!number
- 路径展开:
- 标准输入输出,重定向 Redirection
- 输出重定向:
echo 123 > a.log
,>>
,2>
,2>&1
,&>
- 管道 Pipelines:
ls /usr/bin | wxc -l
- 输入重定向:
python hello.py < foo.txt
xagrs
:把标准输入转化成参数列表- Here Documents:
<<
,<<-
,_EOF_
- Here Strings:
<<<
,read -r -a Words <<< "This is a string of words."
- 输出重定向:
- 控制编码 ANSI escape code
- reset:
\033[0m
- 4-bit black:
\033[0;30m
- foreground rgb:
\x1b[38;2;⟨r⟩;⟨g⟩;⟨b⟩m
- background rgb:
\x1b[48;2;⟨r⟩;⟨g⟩;⟨b⟩m
- 移动光标并清除上一行:
echo "\033[1F\033[2K\c"
- reset:
- 路径
- (执行)
- 权限 Permissions
-rwxr-xr-x
chmod +x ./file
,chmod 755 ./file
- 进程操控 Processes (略)
- 权限 Permissions
- 包管理
- Debian:
apt
,dpkg
- 红帽:
dnf
,yum
,rpm
- Debian:
- Shell Script
- misc
- dotfiles, rc files
- shebang:
#!/usr/bin/env bash
source
,.
:在当前 shell 环境执行脚本#
:注释: ""
:利用 true 写多行注释
- 函数
function name {}
,name () {}
local
- 参数
$0
,$1
,$2
,${99}
- 参数组:
$#
,shift
,$@
,$*
- 退出状态 Exit Status:0-255
- 控制流 Control Flow
- 条件表达式 Conditional Expressions
test expression
,[ expression ]
- 文件表达式、字符串表达式、整型表达式
- 字符串的扩展:
[[ str =~ regex ]]
,[[ $FILE == foo.* ]]
- 整数的扩展:
(( INT < 0 ))
- 逻辑操作符:
&&
,||
,!
if
,while
,continue
,break
,until
case
,;;
,;;&
for
,done
- 条件表达式 Conditional Expressions
read
,IFS
Internal Field Separator- 变量
- 字符串
- (在展开中做各种处理)
- 数字
- 数基/进制:
$((033))
,$((0x1b))
,$((2#11011))
- (各种运算符)
+
,-
,*
,/
,**
,%
=
,+=
,++
,--
˜
,<<
,>>
,&
,|
,^
<
,>
,==
,!=
,&&
,||
expr1?expr2:expr3
- 计算器:
bc <<< "2+3"
- 数基/进制:
- 数组
arr[1]=val
,arr=(foo bar)
${arr[@]}
- (数组展开类似字符串展开)
declare
,unset
- 字符串
- misc
- Misc
- 子 Shell Subshells
- 子进程替换 Process Substitution
read foo < <(echo "bar")
echo "bar" > >(read; echo "foo, $REPLY")
# 代码片段
# proxy
自用,有时需要切换网络连通情况,来回修改 export ALL_PROXY
,所以写了一个脚本来简化流程。
因为一些 subshell 的问题,似乎不太能像其他高级语言一样只 export 一部分变量,
于是目前额外暴露出了_proxy_{set,unset}
两个函数,搜了一圈似乎没什么解决方法,就这样吧…
# https://everything.curl.dev/usingcurl/proxies/env
# * ----------------------------------------------------------------
_proxy_set() {
export ALL_PROXY=$1
export http_proxy=$1
export https_proxy=$1
export HTTP_PROXY=$1
export HTTPS_PROXY=$1
}
# * ----------------------------------------------------------------
_proxy_unset() {
unset ALL_PROXY
unset http_proxy
unset https_proxy
unset HTTP_PROXY
unset HTTPS_PROXY
}
# * ----------------------------------------------------------------
proxy() {
local localproxy='http://127.0.0.1:1080'
local deadproxy="http://a.deadlink/"
case "$1" in
boot | on)
_proxy_set $localproxy
;;
dead)
_proxy_set $deadproxy
;;
off)
_proxy_unset
;;
esac
if [[ $1 == 'boot' ]]; then
echo 'Proxy on'
elif [[ -n $ALL_PROXY ]]; then
echo "Proxy: $ALL_PROXY"
else
echo "Proxy off"
fi
}
# * ----------------------------------------------------------------
proxy boot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# ginit
自用,给当前目录快速初始化 git。
#!/bin/bash
# * ----------------------------------------------------------------
if [[ $(git rev-parse --is-inside-work-tree) ]]; then
echo "Already in a git repository"
exit 1
fi
# * ----------------------------------------------------------------
git_commit_init() {
touch .gitignore
git add .gitignore
git commit -m "feat(init): initial commit"
git tag init
}
# * ----------------------------------------------------------------
git init
if [ -f .gitignore ]; then
TMP_FILE=$(mktemp)
mv .gitignore $TMP_FILE
git_commit_init
mv $TMP_FILE .gitignore
else
git_commit_init
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
上次更新: Nov 18, 2022 10:10 PM