├── .github
└── workflows
│ └── wangdoc.yml
├── .gitignore
├── .travis.yml.bak
├── README.md
├── chapters.yml
├── deploy.sh
├── docs
├── archives
│ ├── archiving.md
│ ├── async.md
│ ├── command.md
│ ├── commands
│ │ ├── alias.md
│ │ ├── awk.md
│ │ ├── cal.md
│ │ ├── cat.md
│ │ ├── clear.md
│ │ ├── cp.md
│ │ ├── cut.md
│ │ ├── date.md
│ │ ├── dd.md
│ │ ├── df.md
│ │ ├── du.md
│ │ ├── egrep.md
│ │ ├── export.md
│ │ ├── file.md
│ │ ├── find.md
│ │ ├── fmt.md
│ │ ├── grep.md
│ │ ├── gunzip.md
│ │ ├── gzcat.md
│ │ ├── gzip.md
│ │ ├── kill.md
│ │ ├── killall.md
│ │ ├── last.md
│ │ ├── lpq.md
│ │ ├── lpr.md
│ │ ├── ls.md
│ │ ├── nl.md
│ │ ├── ps.md
│ │ ├── scp.md
│ │ ├── sed.md
│ │ ├── sort.md
│ │ ├── tr.md
│ │ ├── uname.md
│ │ ├── uniq.md
│ │ ├── uptime.md
│ │ ├── w.md
│ │ ├── wc.md
│ │ ├── whereis.md
│ │ ├── which.md
│ │ └── who.md
│ ├── deleted
│ │ └── stdio.md
│ ├── file-operation.md
│ ├── file.md
│ ├── hardware.md
│ ├── host.md
│ ├── named-pipe.md
│ ├── process.md
│ ├── redirection.md
│ ├── regex.md
│ ├── system.md
│ ├── text.md
│ ├── time.md
│ └── user.md
├── arithmetic.md
├── array.md
├── condition.md
├── debug.md
├── expansion.md
├── function.md
├── grammar.md
├── history.md
├── intro.md
├── loop.md
├── mktemp.md
├── prompt.md
├── quotation.md
├── read.md
├── readline.md
├── script.md
├── set.md
├── stack.md
├── startup.md
├── string.md
└── variable.md
├── loppo.yml
├── package.json
└── wangdoc-deploy-rsa.enc
/.github/workflows/wangdoc.yml:
--------------------------------------------------------------------------------
1 | name: Bash tutorial CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | page-generator:
9 | name: Generating pages
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | with:
15 | persist-credentials: false
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: 'latest'
20 | - name: Install dependencies
21 | run: npm install
22 | - name: Build pages
23 | run: npm run build
24 | - name: Deploy to website
25 | uses: JamesIves/github-pages-deploy-action@v4
26 | with:
27 | git-config-name: wangdoc-bot
28 | git-config-email: yifeng.ruan@gmail.com
29 | repository-name: wangdoc/website
30 | token: ${{ secrets.WANGDOC_BOT_TOKEN }}
31 | branch: master # The branch the action should deploy to.
32 | folder: dist # The folder the action should deploy.
33 | target-folder: dist/bash
34 | clean: true # Automatically remove deleted files from the deploy branch
35 | commit-message: update from Bash tutorial
36 |
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/.travis.yml.bak:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'node'
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | install:
10 | - npm ci
11 | # keep the npm cache around to speed up installs
12 | cache:
13 | directories:
14 | - "$HOME/.npm"
15 |
16 | script: bash ./deploy.sh
17 | env:
18 | global:
19 | - ENCRYPTION_LABEL: 91658d000fbc
20 | - COMMIT_AUTHOR_EMAIL: yifeng.ruan@gmail.com
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 本教程介绍 Linux 命令行 Bash 的基本用法和脚本编程。
2 |
--------------------------------------------------------------------------------
/chapters.yml:
--------------------------------------------------------------------------------
1 | - intro.md: 简介
2 | - grammar.md: 基本语法
3 | - expansion.md: 模式扩展
4 | - quotation.md: 引号和转义
5 | - variable.md: 变量
6 | - string.md: 字符串操作
7 | - arithmetic.md: 算术运算
8 | - history.md: 操作历史
9 | - readline.md: 行操作
10 | - stack.md: 目录堆栈
11 | - script.md: 脚本入门
12 | - read.md: read 命令
13 | - condition.md: 条件判断
14 | - loop.md: 循环
15 | - function.md: 函数
16 | - array.md: 数组
17 | - set.md: set 命令,shopt 命令
18 | - debug.md: 脚本除错
19 | - mktemp.md: mktemp 命令,trap 命令
20 | - startup.md: 启动环境
21 | - prompt.md: 命令提示符
22 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e # Exit with nonzero exit code if anything fails
3 |
4 | # Get the deploy key by using Travis's stored variables to decrypt deploy_key.enc
5 | ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
6 | ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
7 | ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
8 | ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
9 | openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in wangdoc-deploy-rsa.enc -out wangdoc-deploy-rsa -d
10 | chmod 600 wangdoc-deploy-rsa
11 | eval `ssh-agent -s`
12 | ssh-add wangdoc-deploy-rsa
13 |
14 | # Now that we're all set up, we can push.
15 | # git push $SSH_REPO $TARGET_BRANCH
16 | npm run build-and-commit
17 |
--------------------------------------------------------------------------------
/docs/archives/archiving.md:
--------------------------------------------------------------------------------
1 | # 归档和备份
2 |
3 | ## gzip
4 |
5 | gzip 程序用来压缩文件,原文件的压缩版(添加`gz`后缀名)会替代原文件。gunzip 程序用来还原压缩版本。
6 |
7 | ```bash
8 | $ gzip foo.txt
9 | $ gunzip foo.txt.gz
10 | ```
11 |
12 | `gzip`的参数如下。
13 |
14 | - -c 把输出写入到标准输出,并且保留原始文件。也有可能用--stdout 和--to-stdout 选项来指定。
15 | - -d 解压缩。正如 gunzip 命令一样。也可以用--decompress 或者--uncompress 选项来指定.
16 | - -f 强制压缩,即使原始文件的压缩文件已经存在了,也要执行。也可以用--force 选项来指定。
17 | - -h 显示用法信息。也可用--help 选项来指定。
18 | - -l 列出每个被压缩文件的压缩数据。也可用--list 选项。
19 | - -r 若命令的一个或多个参数是目录,则递归地压缩目录中的文件。也可用--recursive 选项来指定。
20 | - -t 测试压缩文件的完整性。也可用--test 选项来指定。
21 | - -v 显示压缩过程中的信息。也可用--verbose 选项来指定。
22 | - -number 设置压缩指数。number 是一个在1(最快,最小压缩)到9(最慢,最大压缩)之间的整数。 数值1和9也可以各自用--fast 和--best 选项来表示。默认值是整数6。
23 |
24 | 下面是一些例子。
25 |
26 | ```bash
27 | # 查看解压缩后的内容
28 | $ gunzip -c foo.txt | less
29 | ```
30 |
31 | `zcat`程序等同于带有-c 选项的 gunzip 命令。它可以像`cat`命令那样,用来查看`gzip`压缩文件。
32 |
33 | ```bash
34 | $ zcat foo.txt.gz | less
35 | ```
36 |
37 | ## bzip2
38 |
39 | `bzip2`程序与`gzip`程序相似,但是使用了不同的压缩算法,舍弃了压缩速度,实现了更高的压缩级别。在大多数情况下,它的工作模式等同于`gzip`。 由`bzip2`压缩的文件,用扩展名`.bz2`表示。
40 |
41 | ```bash
42 | $ bzip2 foo.txt
43 | $ bunzip2 foo.txt.bz2
44 | ```
45 |
46 | gzip程序的所有选项(除了`-r`),bzip2 程序同样也支持。同样有 bunzip2 和 bzcat 程序来解压缩文件。bzip2 文件也带有 bzip2recover 程序,其会 试图恢复受损的 .bz2 文件。
47 |
48 | ## zip
49 |
50 | `zip`程序既是压缩工具,也是一个打包工具,读取和写入.zip文件。
51 |
52 | ```bash
53 | $ zip options zipfile file...
54 | ```
55 |
56 | 它的用法如下。
57 |
58 | ```bash
59 | # 将指定目录压缩成zip文件
60 | $ zip -r playground.zip playground
61 | ```
62 |
63 | `zip`与`tar`命令有一个相反之处。如果压缩文件已存在,其将被更新而不是被替代。这意味着会保留此文件包,但是会添加新文件,同时替换匹配的文件。
64 |
65 | 解压使用`unzip`命令。
66 |
67 | ```bash
68 | $ unzip ../playground.zip
69 | ```
70 |
71 | `unzip`命令的参数如下。
72 |
73 | - `-l` 列出文件包中的内容而不解压
74 | - `-v` 显示冗余信息
75 | - `-p` 输出发送到标准输出
76 |
77 | ```bash
78 | $ unzip -p ls-etc.zip | less
79 | ```
80 |
81 | ## tar
82 |
83 | `tar`是tape archive的简称,原来是一款制作磁带备份的工具,现在主要用于打包。一个 tar 包可以由一组独立的文件,一个或者多个目录,或者两者混合体组成。
84 |
85 | `tar`程序的语法如下。
86 |
87 | ```bash
88 | $ tar mode[options] pathname...
89 | ```
90 |
91 | tar支持以下模式。
92 |
93 | - c 表示create,为文件和/或目录列表创建归档文件。
94 | - x 抽取归档文件。
95 | - r 追加具体的路径到归档文件的末尾。
96 | - t 列出归档文件的内容。
97 |
98 | 支持的参数如下。
99 |
100 | - f 表示file,用来指定生成的文件。
101 |
102 | 模式和参数可以写在一起,而且不需要开头的短横线。注意,必须首先指定模式,然后才是其它的选项。
103 |
104 | ```bash
105 | # 创建子目录的tar包
106 | $ tar cf playground.tar playground
107 |
108 | # 查看tar包内容
109 | $ tar tf playground.tar
110 |
111 | # 查看更详细的列表信息
112 | $ tar tvf playground.tar
113 |
114 | # 还原归档文件
115 | $ tar xf playground.tar
116 |
117 | # 还原单个文件
118 | $ tar xf archive.tar pathname
119 |
120 | # 还原文件到指定目录
121 | $ tar xvf archive.tar -C /home/me/
122 |
123 | # 追加文件
124 | $ tar rf archive.tar file.txt
125 |
126 | # 验证归档文件内容是否正确
127 | $ tar tvfW archive.tar
128 |
129 | # 支持通配符
130 | $ tar xf ../playground2.tar --wildcards 'home/me/playground/\*.txt'
131 | ```
132 |
133 | 注意,`tar`命令还原的时候,总是还原为相对路径。如果归档的时候,保存的是绝对路径,那么还原的时候,这个绝对路径会整个变成相对路径。
134 |
135 | `find`命令可以与`tar`命令配合使用。
136 |
137 | ```bash
138 | $ find playground -name 'file.txt' -exec tar rf playground.tar '{}' '+'
139 | ```
140 |
141 | 上面的命令先用`find`程序找到所有名为`file.txt`的文件,然后使用追加模式(`r`)的`tar`命令,把匹配的文件添加到归档文件`playground.tar`里面。
142 |
143 | 这种`tar`和`find`的配合使用,可以创建逐渐增加的目录树或者整个系统的备份。通过`find`命令匹配新于某个时间戳的文件,我们就能够创建一个归档文件,其只包含新于上一个 tar 包的文件。
144 |
145 | tar支持压缩功能。
146 |
147 | ```bash
148 | # 打成gzip压缩包
149 | $ tar czvf assets.tar.gz dist
150 |
151 | # 打成bz2压缩包
152 | $ tar cvfj assets.tar.bz2 dist
153 |
154 | # 解压 tar.gz 文件
155 | $ tar xzv archive.tar.gz
156 | $ tar xvf archive.tar.gz
157 |
158 | # 解压bz2压缩包
159 | $ tar xvf archive.tar.bz2
160 |
161 | # 显示gzip压缩包内容
162 | $ tar tvf archive.tar.gz
163 |
164 | # 显示bz2压缩包内容
165 | $ tar tvf archive.tar.bz2
166 |
167 | # 从gzip压缩包取出单个文件
168 | $ tar zxvf archive.tar.gz file.txt
169 |
170 | # 从bz2压缩包取出单个文件
171 | $ tar jxvf archive.tar.bz2 file.txt
172 |
173 | # 按通配符取出文件
174 | $ tar zxvf archive.tar.gz --wildcards '*.php'
175 | $ tar jxvf archive.tar.bz2 --wildcards '*.php'
176 |
177 | # 追加文件到压缩包
178 | $ tar rvf archive.tar.gz xyz.txt
179 | $ tar rvf archive.tar.bz2 xyz.txt
180 | ```
181 |
182 | ## rsync
183 |
184 | `rsync`命令用于在多个目录之间、或者本地与远程目录之间同步。字母`r`表示`remote`。
185 |
186 | ```bash
187 | $ rsync options source destination
188 | ```
189 |
190 | source 和 destination 是下列选项之一:
191 |
192 | - 一个本地文件或目录
193 | - 一个远端文件或目录,以`[user@]host:path`的形式存在
194 | - 一个远端 rsync 服务器,由`rsync://[user@]host[:port]/path`指定
195 |
196 | 注意 source 和 destination 两者之一必须是本地文件。rsync 不支持远端到远端的复制。
197 |
198 | `rsync`命令的参数如下。
199 |
200 | - `-a` 递归和保护文件属性
201 | - `-v` 冗余输出
202 | - `--delete` 删除可能在备份设备中已经存在但却不再存在于源设备中的文件
203 | - `--rsh=ssh` 使用 ssh 程序作为远程 shell,目的地必须标注主机名。
204 |
205 | ```bash
206 | # 同步两个本地目录
207 | $ rsync -av playground foo
208 |
209 | # 删除源设备不存在的文件
210 | $ sudo rsync -av --delete /etc /home /usr/local /media/BigDisk/backup
211 |
212 | # 远程同步
213 | $ sudo rsync -av --delete --rsh=ssh /etc /home /usr/local remote-sys:/backup
214 |
215 | # 与远程rsync主机同步
216 | $ rsync -av -delete rsync://rsync.gtlib.gatech.edu/path/to/oss fedora-devel
217 | ```
218 |
--------------------------------------------------------------------------------
/docs/archives/async.md:
--------------------------------------------------------------------------------
1 | # 异步任务
2 |
3 | Bash脚本有时候需要同时执行多个任务。通常这涉及到启动一个脚本,依次,启动一个或多个子脚本来执行额外的任务,而父脚本继续运行。然而,当一系列脚本 以这种方式运行时,要保持父子脚本之间协调工作,会有一些问题。也就是说,若父脚本或子脚本依赖于另一方,并且 一个脚本必须等待另一个脚本结束任务之后,才能完成它自己的任务,这应该怎么办?
4 |
5 | bash 有一个内置命令,能帮助管理诸如此类的异步执行的任务。wait 命令导致一个父脚本暂停运行,直到一个 特定的进程(例如,子脚本)运行结束。
6 |
7 | 首先我们将演示一下 wait 命令的用法。为此,我们需要两个脚本,一个父脚本:
8 |
9 | ```bash
10 | #!/bin/bash
11 | # async-parent : Asynchronous execution demo (parent)
12 | echo "Parent: starting..."
13 | echo "Parent: launching child script..."
14 | async-child &
15 | pid=$!
16 | echo "Parent: child (PID= $pid) launched."
17 | echo "Parent: continuing..."
18 | sleep 2
19 | echo "Parent: pausing to wait for child to finish..."
20 | wait $pid
21 | echo "Parent: child is finished. Continuing..."
22 | echo "Parent: parent is done. Exiting."
23 | ```
24 |
25 | 和一个子脚本:
26 |
27 | ```bash
28 | #!/bin/bash
29 | # async-child : Asynchronous execution demo (child)
30 | echo "Child: child is running..."
31 | sleep 5
32 | echo "Child: child is done. Exiting."
33 | ```
34 |
35 | 在这个例子中,我们看到该子脚本是非常简单的。真正的操作通过父脚本完成。在父脚本中,子脚本被启动, 并被放置到后台运行。子脚本的进程 ID 记录在 pid 变量中,这个变量的值是 $! shell 参数的值,它总是 包含放到后台执行的最后一个任务的进程 ID 号。
36 |
37 | 父脚本继续,然后执行一个以子进程 PID 为参数的 wait 命令。这就导致父脚本暂停运行,直到子脚本退出, 意味着父脚本结束。
38 |
39 | 当执行后,父子脚本产生如下输出:
40 |
41 | ```bash
42 | $ async-parent
43 | Parent: starting...
44 | Parent: launching child script...
45 | Parent: child (PID= 6741) launched.
46 | Parent: continuing...
47 | Child: child is running...
48 | Parent: pausing to wait for child to finish...
49 | Child: child is done. Exiting.
50 | Parent: child is finished. Continuing...
51 | Parent: parent is done. Exiting.
52 | ```
53 |
--------------------------------------------------------------------------------
/docs/archives/command.md:
--------------------------------------------------------------------------------
1 | # Shell 的命令
2 |
3 | ## 命令的类别
4 |
5 | Bash可以使用的命令分成四类。
6 |
7 | - 可执行程序
8 | - Shell 提供的命令
9 | - Shell 函数
10 | - 前三类命令的别名
11 |
12 | ## type, whatis
13 |
14 | `type`命令可以显示命令类型。
15 |
16 | ```bash
17 | $ type command
18 | ```
19 |
20 | 下面是几个例子。
21 |
22 | ```bash
23 | $ type type
24 | type is a shell builtin
25 |
26 | $ type ls
27 | ls is aliased to `ls --color=tty'
28 |
29 | $ type cp
30 | cp is /bin/cp
31 | ```
32 |
33 | `whatis`命令显示指定命令的描述。
34 |
35 | ```bash
36 | $ whatis ls
37 | ls (1) - list directory contents
38 | ```
39 |
40 | ## apropos
41 |
42 | `apropos`命令返回符合搜索条件的命令列表。
43 |
44 | ```bash
45 | $ apropos floppy
46 | create_floppy_devices (8) - udev callout to create all possible
47 | fdformat (8) - Low-level formats a floppy disk
48 | floppy (8) - format floppy disks
49 | gfloppy (1) - a simple floppy formatter for the GNOME
50 | mbadblocks (1) - tests a floppy disk, and marks the bad
51 | mformat (1) - add an MSDOS filesystem to a low-level
52 | ```
53 |
54 | ## alias, unalias
55 |
56 | `alias`命令用来为命令起别名。
57 |
58 | ```bash
59 | $ alias foo='cd /usr; ls; cd -'
60 |
61 | $ type foo
62 | foo is aliased to `cd /usr; ls ; cd -'
63 | ```
64 |
65 | 上面命令指定`foo`为三个命令的别名。以后,执行`foo`就相当于一起执行这三条命令。
66 |
67 | 注意,默认情况下,别名只在当前Session有效。当前Session结束时,这些别名就会消失。
68 |
69 | `alias`命令不加参数时,显示所有有效的别名。
70 |
71 | ```bash
72 | $ alias
73 | alias l.='ls -d .* --color=tty'
74 | alias ll='ls -l --color=tty'
75 | alias ls='ls --color=tty'
76 | ```
77 |
78 | `unalias`命令用来取消别名。
79 |
80 | ```bash
81 | $ unalias foo
82 | $ type foo
83 | bash: type: foo: not found
84 | ```
85 |
86 | ## which
87 |
88 | `which`命令显示可执行程序的路径。
89 |
90 | ```bash
91 | $ which ls
92 | /bin/ls
93 | ```
94 |
95 | `which`命令用于Shell内置命令时(比如`cd`),将没有任何输出。
96 |
97 | ## help,man
98 |
99 | `help`命令用于查看Shell内置命令的帮助信息,`man`命令用于查看可执行命令的帮助信息。
100 |
101 | ```bash
102 | $ help cd
103 | $ man ls
104 | ```
105 |
106 | `man`里面的文档一共有8类,如果同一个命令,匹配多个文档,`man`命令总是返回第一个匹配。如果想看指定类型的文档,命令可以采用下面的形式。
107 |
108 | ```bash
109 | $ man 5 passwd
110 | ```
111 |
112 | ## script
113 |
114 | `script`命令会将输入的命令和它的输出,都保存进一个文件。
115 |
116 | ```bash
117 | $ script [file]
118 | ```
119 |
120 | 如果没有指定文件名,则所有结果会保存进当前目录下`typescript`文件。结束录制的时候,可以按下`Ctrl + d`。
121 |
122 | ## export
123 |
124 | `export`命令用于将当前进程的变量,输出到所有子进程。
125 |
126 | ## 命令的连续执行
127 |
128 | 多个命令可以写在一起。
129 |
130 | Bash 提供三种方式,定义它们如何执行。
131 |
132 | ```bash
133 | # 第一个命令执行完,执行第二个命令
134 | command1; command2
135 |
136 | # 只有第一个命令成功执行完(退出码0),才会执行第二个命令
137 | command1 && command2
138 |
139 | # 只有第一个命令执行失败(退出码非0),才会执行第二个命令
140 | command1 || command2
141 | ```
142 |
143 | 上面三种执行方法的退出码,都是最后一条执行的命令的退出码。
144 |
145 | bash 允许把命令组合在一起。可以通过两种方式完成;要么用一个 group 命令,要么用一个子 shell。 这里是每种方式的语法示例:
146 |
147 | 组命令:
148 |
149 | ```bash
150 | { command1; command2; [command3; ...] }
151 | ```
152 |
153 | 子 shell
154 |
155 | ```bash
156 | (command1; command2; [command3;...])
157 | ```
158 |
159 | 这两种形式的不同之处在于,组命令用花括号把它的命令包裹起来,而子 shell 用括号。值得注意的是,鉴于 bash 实现组命令的方式, 花括号与命令之间必须有一个空格,并且最后一个命令必须用一个分号或者一个换行符终止。
160 |
161 | 那么组命令和子 shell 命令对什么有好处呢? 它们都是用来管理重定向的。
162 |
163 | ```bash
164 | { ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt
165 | ```
166 |
167 | 使用一个子 shell 是相似的。
168 |
169 | ```bash
170 | (ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt
171 | ```
172 |
173 | 组命令和子 shell 真正闪光的地方是与管道线相结合。 当构建一个管道线命令的时候,通常把几个命令的输出结果合并成一个流是很有用的。 组命令和子 shell 使这种操作变得很简单。
174 |
175 | ```bash
176 | { ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr
177 | ```
178 |
179 | 这里我们已经把我们的三个命令的输出结果合并在一起,并把它们用管道输送给命令 lpr 的输入,以便产生一个打印报告。
180 |
181 | 虽然组命令和子 shell 看起来相似,并且它们都能用来在重定向中合并流,但是两者之间有一个很重要的不同。 然而,一个组命令在当前 shell 中执行它的所有命令,而一个子 shell(顾名思义)在当前 shell 的一个 子副本中执行它的命令。这意味着运行环境被复制给了一个新的 shell 实例。当这个子 shell 退出时,环境副本会消失, 所以在子 shell 环境(包括变量赋值)中的任何更改也会消失。因此,在大多数情况下,除非脚本要求一个子 shell, 组命令比子 shell 更受欢迎。组命令运行很快并且占用的内存也少。
182 |
183 | 当我们发现管道线中的一个 read 命令 不按我们所期望的那样工作的时候。为了重现问题,我们构建一个像这样的管道线:
184 |
185 | ```bash
186 | echo "foo" | read
187 | echo $REPLY
188 | ```
189 |
190 | 该 REPLY 变量的内容总是为空,是因为这个 read 命令在一个子 shell 中执行,所以它的 REPLY 副本会被毁掉, 当该子 shell 终止的时候。因为管道线中的命令总是在子 shell 中执行,任何给变量赋值的命令都会遭遇这样的问题。 幸运地是,shell 提供了一种奇异的展开方式,叫做进程替换,它可以用来解决这种麻烦。进程替换有两种表达方式:
191 |
192 | 一种适用于产生标准输出的进程:
193 |
194 | ```bash
195 | <(list)
196 | ```
197 |
198 | 另一种适用于接受标准输入的进程:
199 |
200 | ```bash
201 | >(list)
202 | ```
203 |
204 | 这里的 list 是一串命令列表:
205 |
206 | 为了解决我们的 read 命令问题,我们可以雇佣进程替换,像这样。
207 |
208 | ```bash
209 | read < <(echo "foo")
210 | echo $REPLY
211 | ```
212 |
213 | 进程替换允许我们把一个子 shell 的输出结果当作一个用于重定向的普通文件。事实上,因为它是一种展开形式,我们可以检验它的真实值:
214 |
215 | ```bash
216 | [me@linuxbox ~]$ echo <(echo "foo")
217 | /dev/fd/63
218 | ```
219 |
220 | 通过使用 echo 命令,查看展开结果,我们看到子 shell 的输出结果,由一个名为 /dev/fd/63 的文件提供。
221 |
--------------------------------------------------------------------------------
/docs/archives/commands/alias.md:
--------------------------------------------------------------------------------
1 | # alias
2 |
3 | `alias`命令用于设置别名。通常用于在 Bash 设置文件中,设置别名。
4 |
5 | ```bash
6 | alias dockerlogin='ssh www-data@adnan.local -p2222'
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/awk.md:
--------------------------------------------------------------------------------
1 | # awk
2 |
3 | [`awk`](https://en.wikipedia.org/wiki/AWK)是处理文本文件的一个应用程序,几乎所有 Linux 系统都自带这个程序。
4 |
5 | 它依次处理文件的每一行,并读取里面的每一个字段。对于日志、CSV 那样的每行格式相同的文本文件,`awk`可能是最方便的工具。
6 |
7 | 
8 |
9 | `awk`其实不仅仅是工具软件,还是一种编程语言。不过,这里只介绍它的命令行用法,对于大多数场合,应该足够用了。
10 |
11 | ## 基本用法
12 |
13 | `awk`的基本用法就是下面的形式。
14 |
15 | ```bash
16 | # 格式
17 | $ awk 动作 文件名
18 |
19 | # 示例
20 | $ awk '{print $0}' demo.txt
21 | ```
22 |
23 | 上面示例中,`demo.txt`是`awk`所要处理的文本文件。前面单引号内部有一个大括号,里面就是每一行的处理动作`print $0`。其中,`print`是打印命令,`$0`代表当前行,因此上面命令的执行结果,就是把每一行原样打印出来。
24 |
25 | 下面,我们先用标准输入(stdin)演示上面这个例子。
26 |
27 | ```bash
28 | $ echo 'this is a test' | awk '{print $0}'
29 | this is a test
30 | ```
31 |
32 | 上面代码中,`print $0`就是把标准输入`this is a test`,重新打印了一遍。
33 |
34 | `awk`会根据空格和制表符,将每一行分成若干字段,依次用`$1`、`$2`、`$3`代表第一个字段、第二个字段、第三个字段等等。
35 |
36 | ```bash
37 | $ echo 'this is a test' | awk '{print $3}'
38 | a
39 | ```
40 |
41 | 上面代码中,`$3`代表`this is a test`的第三个字段`a`。
42 |
43 | 下面,为了便于举例,我们把`/etc/passwd`文件保存成`demo.txt`。
44 |
45 | ```bash
46 | root:x:0:0:root:/root:/usr/bin/zsh
47 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
48 | bin:x:2:2:bin:/bin:/usr/sbin/nologin
49 | sys:x:3:3:sys:/dev:/usr/sbin/nologin
50 | sync:x:4:65534:sync:/bin:/bin/sync
51 | ```
52 |
53 | 这个文件的字段分隔符是冒号(`:`),所以要用`-F`参数指定分隔符为冒号。然后,才能提取到它的第一个字段。
54 |
55 | ```bash
56 | $ awk -F ':' '{ print $1 }' demo.txt
57 | root
58 | daemon
59 | bin
60 | sys
61 | sync
62 | ```
63 |
64 | ## 变量
65 |
66 | 除了`$ + 数字`表示某个字段,`awk`还提供其他一些变量。
67 |
68 | 变量`NF`表示当前行有多少个字段,因此`$NF`就代表最后一个字段。
69 |
70 | ```bash
71 | $ echo 'this is a test' | awk '{print $NF}'
72 | test
73 | ```
74 |
75 | `$(NF-1)`代表倒数第二个字段。
76 |
77 | ```
78 | $ awk -F ':' '{print $1, $(NF-1)}' demo.txt
79 | root /root
80 | daemon /usr/sbin
81 | bin /bin
82 | sys /dev
83 | sync /bin
84 | ```
85 |
86 | 上面代码中,`print`命令里面的逗号,表示输出的时候,两个部分之间使用空格分隔。
87 |
88 | 变量`NR`表示当前处理的是第几行。
89 |
90 | ```bash
91 | $ awk -F ':' '{print NR ") " $1}' demo.txt
92 | 1) root
93 | 2) daemon
94 | 3) bin
95 | 4) sys
96 | 5) sync
97 | ```
98 |
99 | 上面代码中,`print`命令里面,如果原样输出字符,要放在双引号里面。
100 |
101 | `awk`的其他内置变量如下。
102 |
103 | > - `FILENAME`:当前文件名
104 | > - `FS`:字段分隔符,默认是空格和制表符。
105 | > - `RS`:行分隔符,用于分割每一行,默认是换行符。
106 | > - `OFS`:输出字段的分隔符,用于打印时分隔字段,默认为空格。
107 | > - `ORS`:输出记录的分隔符,用于打印时分隔记录,默认为换行符。
108 | > - `OFMT`:数字输出的格式,默认为`%.6g`。
109 |
110 | ## 函数
111 |
112 | `awk`还提供了一些内置函数,方便对原始数据的处理。
113 |
114 | 函数`toupper()`用于将字符转为大写。
115 |
116 | ```bash
117 | $ awk -F ':' '{ print toupper($1) }' demo.txt
118 | ROOT
119 | DAEMON
120 | BIN
121 | SYS
122 | SYNC
123 | ```
124 |
125 | 上面代码中,第一个字段输出时都变成了大写。
126 |
127 | 其他常用函数如下。
128 |
129 | > - `tolower()`:字符转为小写。
130 | > - `length()`:返回字符串长度。
131 | > - `substr()`:返回子字符串。
132 | > - `sin()`:正弦。
133 | > - `cos()`:余弦。
134 | > - `sqrt()`:平方根。
135 | > - `rand()`:随机数。
136 |
137 | `awk`内置函数的完整列表,可以查看[手册](https://www.gnu.org/software/gawk/manual/html_node/Built_002din.html#Built_002din)。
138 |
139 | ## 条件
140 |
141 | `awk`允许指定输出条件,只输出符合条件的行。
142 |
143 | 输出条件要写在动作的前面。
144 |
145 | ```bash
146 | $ awk '条件 动作' 文件名
147 | ```
148 |
149 | 请看下面的例子。
150 |
151 | ```bash
152 | $ awk -F ':' '/usr/ {print $1}' demo.txt
153 | root
154 | daemon
155 | bin
156 | sys
157 | ```
158 |
159 | 上面代码中,`print`命令前面是一个正则表达式,只输出包含`usr`的行。
160 |
161 | 下面的例子只输出奇数行,以及输出第三行以后的行。
162 |
163 | ```bash
164 | # 输出奇数行
165 | $ awk -F ':' 'NR % 2 == 1 {print $1}' demo.txt
166 | root
167 | bin
168 | sync
169 |
170 | # 输出第三行以后的行
171 | $ awk -F ':' 'NR >3 {print $1}' demo.txt
172 | sys
173 | sync
174 | ```
175 |
176 | 下面的例子输出第一个字段等于指定值的行。
177 |
178 | ```bash
179 | $ awk -F ':' '$1 == "root" {print $1}' demo.txt
180 | root
181 |
182 | $ awk -F ':' '$1 == "root" || $1 == "bin" {print $1}' demo.txt
183 | root
184 | bin
185 | ```
186 |
187 | ## if 语句
188 |
189 | `awk`提供了`if`结构,用于编写复杂的条件。
190 |
191 | ```bash
192 | $ awk -F ':' '{if ($1 > "m") print $1}' demo.txt
193 | root
194 | sys
195 | sync
196 | ```
197 |
198 | 上面代码输出第一个字段的第一个字符大于`m`的行。
199 |
200 | `if`结构还可以指定`else`部分。
201 |
202 | ```bash
203 | $ awk -F ':' '{if ($1 > "m") print $1; else print "---"}' demo.txt
204 | root
205 | ---
206 | ---
207 | sys
208 | sync
209 | ```
210 |
211 | ## 参考链接
212 |
213 | - [An Awk tutorial by Example](https://gregable.com/2010/09/why-you-should-know-just-little-awk.html), Greg Grothaus
214 | - [30 Examples for Awk Command in Text Processing](https://likegeeks.com/awk-command/), Mokhtar Ebrahim
215 |
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/docs/archives/commands/cal.md:
--------------------------------------------------------------------------------
1 | # cal
2 |
3 | `cal`命令显示本月的日历。
4 |
5 | ```bash
6 | $ cal
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/cat.md:
--------------------------------------------------------------------------------
1 | # cat
2 |
3 | `cat`命令用于显示一个文本文件的内容。
4 |
5 | `cat - >> filename`用于向一个现有文件的尾部追加内容。
6 |
--------------------------------------------------------------------------------
/docs/archives/commands/clear.md:
--------------------------------------------------------------------------------
1 | # clear
2 |
3 | `clear`命令用来清除当前屏幕的显示,运行后会只留下一个提示符。
4 |
5 | ```bash
6 | $ clear
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/cp.md:
--------------------------------------------------------------------------------
1 | # cp 命令
2 |
3 | `cp`命令用于复制文件。
4 |
5 | ## 参数
6 |
7 | `-u`参数只复制那些目标目录里面还不存在的文件,以及那些虽然存在、但是比源目录对应文件更陈旧的文件。
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/cut.md:
--------------------------------------------------------------------------------
1 | # cut
2 |
3 | `cut`命令用于在命令行输出文本文件的指定位置的内容。
4 |
5 | 它的使用格式如下。
6 |
7 | ```bash
8 | $ cut OPTION [FILE]
9 | ```
10 |
11 | 如果没有指定文件名,将读取标准输入。
12 |
13 | `-b`参数用来指定读取的字节。
14 |
15 | ```bash
16 | # 输出前三个字节
17 | $ cut file1.txt -b1,2,3
18 |
19 | # 输出前十个字节
20 | $ cut file1.txt -b1-10
21 |
22 | # 输出从第5个字节开始的所有字节
23 | $ cut file1.txt -b5-
24 |
25 | # 输出前5个字节
26 | $ cut file1.txt -b-5
27 | ```
28 |
29 | `-c`参数用来指定读取的字符,用法与`-b`一样。有的字符是多字节字符,这时候就应该用`-c`代替`-b`。
30 |
31 | `-d`参数用来指定分隔符,默认分隔符为制表符。
32 |
33 | `-f`参数用来指定字段。
34 |
35 | ```bash
36 | # 指定每一行的分隔符为逗号,
37 | # 输出第一和第三个字段
38 | $ cut file1.txt -d, -f1,3
39 |
40 | # 输出第一、第二、第四和第五个字段
41 | $ cut -f 1-2,4-5 data.txt
42 | ```
43 |
44 |
--------------------------------------------------------------------------------
/docs/archives/commands/date.md:
--------------------------------------------------------------------------------
1 | # date
2 |
3 | `date`命令显示当前的日期和时间。
4 |
5 | ```bash
6 | $ date
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/dd.md:
--------------------------------------------------------------------------------
1 | # dd
2 |
3 | `dd`命令用于复制磁盘或文件系统。
4 |
5 | ## 复制磁盘
6 |
7 | ```bash
8 | $ dd if=/dev/sda of=/dev/sdb
9 | ```
10 |
11 | 上面命令表示将`/dev/sda`磁盘复制到`/dev/sdb`设备。参数`if`表示来源地,`of`表示目的地。
12 |
13 | 除了复制,`dd`还允许将磁盘做成一个镜像文件。
14 |
15 | ```bash
16 | $ dd if=/dev/sda of=/home/username/sdadisk.img
17 | ```
18 |
19 | `dd`还可以复制单个分区。
20 |
21 | ```bash
22 | $ dd if=/dev/sda2 of=/home/username/partition2.img bs=4096
23 | ```
24 |
25 | 上面命令中,参数`bs`表示单次拷贝的字节数(bytes)。
26 |
27 | 要将镜像文件复原,也很简单。
28 |
29 | ```bash
30 | $ dd if=sdadisk.img of=/dev/sdb
31 | ```
32 |
33 | ## 清除数据
34 |
35 | `dd`也可以用于清除磁盘数据。
36 |
37 | ```bash
38 | # 磁盘数据写满 0
39 | $ dd if=/dev/zero of=/dev/sda1
40 |
41 | # 磁盘数据写满随机字符
42 | $ dd if=/dev/urandom of=/dev/sda1
43 | ```
44 |
45 | ## 监控进展
46 |
47 | 磁盘的复制通常需要很久,为了监控进展,可以使用 Pipe Viewer 工具软件。如果没有安装这个软件,可以使用下面的命令安装。
48 |
49 | ```bash
50 | $ sudo apt install pv
51 | ```
52 |
53 | 然后,来源地和目的地之间插入广告,就可以看到进展了。
54 |
55 | ```bash
56 | $ dd if=/dev/urandom | pv | dd of=/dev/sda1
57 | 4,14MB 0:00:05 [ 98kB/s] [ <=> ]
58 | ```
59 |
60 | ## 参考链接
61 |
62 | - David Clinton, [How to use dd in Linux without destroying your disk](https://opensource.com/article/18/7/how-use-dd-linux)
63 |
--------------------------------------------------------------------------------
/docs/archives/commands/df.md:
--------------------------------------------------------------------------------
1 | # df
2 |
3 | `df`命令显示磁盘信息。
4 |
--------------------------------------------------------------------------------
/docs/archives/commands/du.md:
--------------------------------------------------------------------------------
1 | # du
2 |
3 | `du`命令显示某个文件或目录的磁盘使用量。
4 |
5 | ```bash
6 | $ du filename
7 | ```
8 |
9 | `-h`参数将返回的大小显示为人类可读的格式,即显示单位为 K、M、G 等。
10 |
11 | `-s`参数表示总结(summarize)。
12 |
13 | `-x`参数表示不显示不在当前分区的目录,通常会忽略`/dev`、`/proc`、`/sys`等目录。
14 |
15 | `-c`参数表示显示当前目录总共占用的空间大小。
16 |
17 | ```bash
18 | # 显示根目录下各级目录占用的空间大小
19 | $ sudo du -shxc /*
20 | ```
21 |
22 | `--exclude`参数用于排除某些目录或文件。
23 |
24 | ```bash
25 | $ sudo du -shxc /* --exclude=proc
26 | $ sudo du -sh --exclude=*.iso
27 | ```
28 |
29 | `--max-depth`参数用于设定目录大小统计到第几层。如果设为`-–max-depth=0`,那么等同于`-s`参数。
30 |
31 | ```bash
32 | $ sudo du /home/ -hc --max-depth=2
33 | ```
34 |
--------------------------------------------------------------------------------
/docs/archives/commands/egrep.md:
--------------------------------------------------------------------------------
1 | # egrep
2 |
3 | `egrep`命令用于显示匹配正则模式的行,与`grep -E`命令等价。
4 |
5 | 下面是`example.txt`文件的内容。
6 |
7 | ```
8 | Lorem ipsum
9 | dolor sit amet,
10 | consetetur
11 | sadipscing elitr,
12 | sed diam nonumy
13 | eirmod tempor
14 | invidunt ut labore
15 | et dolore magna
16 | aliquyam erat, sed
17 | diam voluptua. At
18 | vero eos et
19 | accusam et justo
20 | duo dolores et ea
21 | rebum. Stet clita
22 | kasd gubergren,
23 | no sea takimata
24 | sanctus est Lorem
25 | ipsum dolor sit
26 | amet.
27 | ```
28 |
29 | `egrep`命令显示包括`Lorem`或`dolor`的行。
30 |
31 | ```bash
32 | $ egrep '(Lorem|dolor)' example.txt
33 | # 或者
34 | $ grep -E '(Lorem|dolor)' example.txt
35 | Lorem ipsum
36 | dolor sit amet,
37 | et dolore magna
38 | duo dolores et ea
39 | sanctus est Lorem
40 | ipsum dolor sit
41 | ```
42 |
43 |
--------------------------------------------------------------------------------
/docs/archives/commands/export.md:
--------------------------------------------------------------------------------
1 | # export
2 |
3 | `export`命令用于向子Shell输出变量。
4 |
5 | ```bash
6 | $ export hotellogs="/workspace/hotel-api/storage/logs"
7 | ```
8 |
9 | 然后执行下面的命令,新建一个子 Shell。
10 |
11 | ```bash
12 | $ bash
13 | $ cd hotellogs
14 | ```
15 |
16 | 上面命令的执行结果会进入`hotellogs`变量指向的目录。
17 |
18 | `export`命令还可以显示所有环境变量。
19 |
20 | ```bash
21 | $ export
22 | SHELL=/bin/zsh
23 | AWS_HOME=/Users/adnanadnan/.aws
24 | LANG=en_US.UTF-8
25 | LC_CTYPE=en_US.UTF-8
26 | LESS=-R
27 | ```
28 |
29 | 如果想查看单个变量,使用`echo $VARIABLE_NAME`。
30 |
31 | ```bash
32 | $ echo $SHELL
33 | /usr/bin/zsh
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/archives/commands/file.md:
--------------------------------------------------------------------------------
1 | # file
2 |
3 | `file`命令用来查看某个文件的类型。
4 |
5 | ```bash
6 | $ file index.html
7 | index.html: HTML document, ASCII text
8 | ```
9 |
10 | file 工具可以对所给的文件输出一行简短的介绍,它用文件后缀、头部信息和一些其他的线索来判断文件。你在检查一堆你不熟悉的文件时使用 find 非常方便:
11 |
12 | ```bash
13 | $ find -exec file {} \;
14 | .: directory
15 | ./hanoi: Perl script, ASCII text executable
16 | ./.hanoi.swp: Vim swap file, version 7.3
17 | ./factorial: Perl script, ASCII text executable
18 | ./bits.c: C source, ASCII text
19 | ./bits: ELF 32-bit LSB executable, Intel 80386, version ...
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/archives/commands/find.md:
--------------------------------------------------------------------------------
1 | # find
2 |
3 | `find`命令用于寻找文件,会包括当前目录的所有下级目录。
4 |
5 | 如果不带任何参数,`find`文件会列出当前目录(包含子目录)的所有文件,甚至还包括相对路径。这时把结果使用 sort 排序,效果会更好。
6 |
7 | ```bash
8 | $ find | sort
9 | .
10 | ./Makefile
11 | ./README
12 | ./build
13 | ./client.c
14 | ./client.h
15 | ./common.h
16 | ./project.c
17 | ./server.c
18 | ./server.h
19 | ./tests
20 | ./tests/suite1.pl
21 | ./tests/suite2.pl
22 | ./tests/suite3.pl
23 | ./tests/suite4.pl
24 | ```
25 |
26 | 如果想要`ls -l`样式的列表,只要在 find 后面加上 -ls。
27 |
28 | ```bash
29 | $ find -ls
30 | ```
31 |
32 | find 有它自己的一套复杂的过滤(文件)语句。下面是一些最常用的获取某些文件列表的过滤器。
33 |
34 | - find -name '*.c' —— 查找符合某 shell 式样式的文件名的文件。用 iname 开启大小写不敏感搜索。
35 | - find -path '*test*' —— 查找符合某 shell 式样式的路径的文件。用 ipath 开启大小写不敏感搜索。
36 | - find -mtime -5 —— 查找近五天内编辑过的文件。你也可以用 +5 来查找五天之前编辑过的文件。
37 | - find -newer server.c —— 查找比 server.c 更新的文件。
38 | - find -type d —— 查找所有文件夹。如果想找出所有文件,那就用 -type f;找符号连接就用 -type l。
39 |
40 | 注意,上面这些过滤器都可以组合使用。下面例子是找出近两天内编辑过的`*.c`文件。
41 |
42 | ```bash
43 | $ find -name '*.c' -mtime -2
44 | ```
45 |
46 | 默认情况下, find 对搜索结果所采取的动作只是简单地通过标准输出输出一个列表,然而其实还有其他一些有用的后续动作。
47 |
48 | - -ls —— 如前文,提供了一种类 ls -l 式的列表。
49 | - -delete —— 删除符合查找条件的文件。
50 | - -exec —— 对搜索结果里的每个文件都运行某个命令, `{}`会被替换成适当的文件名,并且命令用`\;`终结。
51 |
52 | ```bash
53 | $ find -name '*.pl' -exec perl -c {} \;
54 | ```
55 |
56 | 你也可以使用`+`作为终止符来对所有结果运行一次命令。我还发现一个我经常使用的小技巧,就是用 find 生成一个文件列表,然后在 Vim 的垂直分窗中编辑:
57 |
58 | ```bash
59 | $ find -name '*.c' -exec vim {} +
60 | ```
61 |
--------------------------------------------------------------------------------
/docs/archives/commands/fmt.md:
--------------------------------------------------------------------------------
1 | # fmt
2 |
3 | `fmt`命令用于对文本指定样式。
4 |
5 | 下面是`example.txt`的内容,是非常长的一行。
6 |
7 | ```
8 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
9 | ```
10 |
11 | `fmt`可以将其输出为每行80个字符。
12 |
13 | ```bash
14 | cat example.txt | fmt -w 20
15 | Lorem ipsum
16 | dolor sit amet,
17 | consetetur
18 | sadipscing elitr,
19 | sed diam nonumy
20 | eirmod tempor
21 | invidunt ut labore
22 | et dolore magna
23 | aliquyam erat, sed
24 | diam voluptua. At
25 | vero eos et
26 | accusam et justo
27 | duo dolores et ea
28 | rebum. Stet clita
29 | kasd gubergren,
30 | no sea takimata
31 | sanctus est Lorem
32 | ipsum dolor sit
33 | amet.
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/archives/commands/grep.md:
--------------------------------------------------------------------------------
1 | # grep
2 |
3 | `grep`命令用于文件内容的搜索,返回所有匹配的行。
4 |
5 | ```bash
6 | $ grep pattern filename
7 | ```
8 |
9 | 下面是一个例子。
10 |
11 | ```bash
12 | $ grep admin /etc/passwd
13 | _kadmin_admin:*:218:-2:Kerberos Admin Service:/var/empty:/usr/bin/false
14 | _kadmin_changepw:*:219:-2:Kerberos Change Password Service:/var/empty:/usr/bin/false
15 | _krb_kadmin:*:231:-2:Open Directory Kerberos Admin Service:/var/empty:/usr/bin/false
16 | ```
17 |
18 | 一般情况下,应该使用`grep -R`,递归地找出当前目录下符合`someVar`的文件。
19 |
20 | ```bash
21 | $ grep -FR 'someVar' .
22 | ```
23 |
24 | 別忘了大小不敏感的参数,因为 `grep` 默认搜索是大小写敏感的。
25 |
26 | ```bash
27 | $ grep -iR 'somevar' .
28 | ```
29 |
30 | 也可以用`grep -l`光打印出符合条件的文件名而非文件内容选段。
31 |
32 | ```bash
33 | $ grep -lR 'somevar' .
34 | ```
35 |
36 | 如果你写的脚本或批处理任务需要上面的输出内容,可以使用 `while` 和 `read` 来处理文件名中的空格和其他特殊字符:
37 |
38 | ```bash
39 | grep -lR someVar | while IFS= read -r file; do
40 | head "$file"
41 | done
42 | ```
43 |
44 | 如果你在你的项目里使用了版本控制软件,它通常会在 `.svn`, `.git`, `.hg` 目录下包含一些元数据。你也可以很容易地用 `grep -v` 把这些目录移出搜索范围,当然得用 `grep -F` 指定一个恰当且确定的字符串,即要移除的目录名:
45 |
46 | ```bash
47 | $ grep -R 'someVar' . | grep -vF '.svn'
48 | ```
49 |
50 | 部分版本的 `grep` 包含了 `--exclude` 和 `--exclude-dir` 选项,这看起来更加易读。
51 |
52 | ## 参数
53 |
54 | `-i`参数表示忽略大小写。
55 |
56 | `-r`表示搜索某个目录下面的所有文件。
57 |
58 | ```bash
59 | $ grep -r admin /etc/
60 | ```
61 |
62 | `-v`过滤包含某个词的行,即`grep`的逆操作。
63 |
64 | ```bash
65 | # 显示所有包含 vim,但不包含 grep 的行
66 | $ ps | grep vim | grep -v grep
67 | ```
68 |
--------------------------------------------------------------------------------
/docs/archives/commands/gunzip.md:
--------------------------------------------------------------------------------
1 | # gunzip
2 |
3 | `gunzip`命令用于解压`gzip`命令压缩的文件。
4 |
--------------------------------------------------------------------------------
/docs/archives/commands/gzcat.md:
--------------------------------------------------------------------------------
1 | # gzcat
2 |
3 | `gzcat`命令用于查看一个`gz`文件,但并不实际解压它。
4 |
5 | ```bash
6 | $ gzcat filename
7 | ```
8 |
9 |
--------------------------------------------------------------------------------
/docs/archives/commands/gzip.md:
--------------------------------------------------------------------------------
1 | # gzip
2 |
3 | `gzip`命令用于压缩文件。
4 |
--------------------------------------------------------------------------------
/docs/archives/commands/kill.md:
--------------------------------------------------------------------------------
1 | # kill
2 |
3 | `kill`命令用户终止指定进程。
4 |
5 | ```bash
6 | $ kill PID
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/killall.md:
--------------------------------------------------------------------------------
1 | # killall
2 |
3 | `killall`命令终止给定名字的一系列相关进程。
4 |
5 | ```bash
6 | $ killall processname
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/last.md:
--------------------------------------------------------------------------------
1 | # last
2 |
3 | `last`命令显示用户登录系统的记录。
4 |
5 | ```bash
6 | $ last
7 | ```
8 |
9 | `last`命令后面加上用户名,会显示该用户上次登录的信息。
10 |
11 | ```bash
12 | $ last yourUsername
13 | ```
14 |
15 |
--------------------------------------------------------------------------------
/docs/archives/commands/lpq.md:
--------------------------------------------------------------------------------
1 | # lpq
2 |
3 | `lpq`命令显示打印机队列。
4 |
5 | ```bash
6 | $ lpq
7 | Rank Owner Job File(s) Total Size
8 | active adnanad 59 demo 399360 bytes
9 | 1st adnanad 60 (stdin) 0 bytes
10 | ```
11 |
--------------------------------------------------------------------------------
/docs/archives/commands/lpr.md:
--------------------------------------------------------------------------------
1 | # lpr
2 |
3 | `lpr`命令用于打印文件。
4 |
5 | ```bash
6 | lpr filename
7 | ```
8 |
9 |
--------------------------------------------------------------------------------
/docs/archives/commands/ls.md:
--------------------------------------------------------------------------------
1 | # ls
2 |
3 | `ls`命令用于列出当前目录里面的文件和子目录。
4 |
5 | ## 参数
6 |
7 | - a:列出隐藏文件
8 | - l:以长格式列出文件
9 | - t:按最后编辑日期排序,最新的最先。这在某个大目录里找出最近修改的文件列表时很有用,比如将结果导入( pipe ) head 或者 sed 10q。或许加上 -l 会效果更好。当然如果你想获取最旧的文件列表,只要加 -r 反转列表即可。
10 | - X:按文件类型分类。这在多语言或多后缀的项目中特别方便,比如头文件和源文件分开,或区分开源文件和生成文件或目录。
11 | - v:按照文件名里的版本号排序。
12 | - S:按文件大小排序。
13 | - R:递归地列举文件。这个选项和 -l 组合使用并将结果导出到 less 效果很好。
14 |
15 | 可以把结果导出给类似 vim 的进程。
16 |
17 | ```bash
18 | $ ls -XR | vim -
19 | ```
20 |
--------------------------------------------------------------------------------
/docs/archives/commands/nl.md:
--------------------------------------------------------------------------------
1 | # nl
2 |
3 | `nl`命令用于显示行号。
4 |
5 | 下面是`example.txt`文件的内容。
6 |
7 | ```bash
8 | Lorem ipsum
9 | dolor sit amet,
10 | consetetur
11 | sadipscing elitr,
12 | sed diam nonumy
13 | eirmod tempor
14 | invidunt ut labore
15 | et dolore magna
16 | aliquyam erat, sed
17 | diam voluptua. At
18 | vero eos et
19 | accusam et justo
20 | duo dolores et ea
21 | rebum. Stet clita
22 | kasd gubergren,
23 | no sea takimata
24 | sanctus est Lorem
25 | ipsum dolor sit
26 | amet.
27 | ```
28 |
29 | `nl`命令让上面这段文本显示行号。
30 |
31 | ```bash
32 | $ nl -s". " example.txt
33 | 1. Lorem ipsum
34 | 2. dolor sit amet,
35 | 3. consetetur
36 | 4. sadipscing elitr,
37 | 5. sed diam nonumy
38 | 6. eirmod tempor
39 | 7. invidunt ut labore
40 | 8. et dolore magna
41 | 9. aliquyam erat, sed
42 | 10. diam voluptua. At
43 | 11. vero eos et
44 | 12. accusam et justo
45 | 13. duo dolores et ea
46 | 14. rebum. Stet clita
47 | 15. kasd gubergren,
48 | 16. no sea takimata
49 | 17. sanctus est Lorem
50 | 18. ipsum dolor sit
51 | 19. amet.
52 | ```
53 |
54 | `-s`参数表示行号的后缀。
55 |
--------------------------------------------------------------------------------
/docs/archives/commands/ps.md:
--------------------------------------------------------------------------------
1 | # ps
2 |
3 | `ps`命令列出当前正在执行的进程信息。
4 |
5 | 由于进程很多,所以为了快速找到某个进程,一般与`grep`配合使用。
6 |
7 | ```bash
8 | # 找出正在运行 vim 的进程
9 | $ ps | grep vi
10 | ```
11 |
12 | ## 参数
13 |
14 | `-u`参数列出指定用户拥有的进程。
15 |
16 | ```bash
17 | $ ps -u yourusername
18 | ```
19 |
20 |
--------------------------------------------------------------------------------
/docs/archives/commands/scp.md:
--------------------------------------------------------------------------------
1 | # scp
2 |
3 | ## 基本用法
4 |
5 | `scp`是 secure copy 的缩写,用来在两台主机之间加密传送文件。它的底层是 SSH 协议,默认端口是22。
6 |
7 | 它主要用于以下三种复制操作。
8 |
9 | - 从本地系统到远程系统。
10 | - 从远程系统到本地系统。
11 | - 在本地系统的两个远程系统之间。
12 |
13 | 使用`scp`传输数据时,文件和密码都是加密的,不会泄漏敏感信息。
14 |
15 | `scp`的语法类似`cp`的语法。
16 |
17 | 注意,如果传输的文件在本机和远程系统,有相同的名称和位置,`scp`会在没有警告的情况下覆盖文件。
18 |
19 | **(1)本地文件复制到远程系统**
20 |
21 | 复制本机文件到远程系统的基本语法如下。
22 |
23 | ```bash
24 | # 语法
25 | $ scp SourceFile user@host:directory/TargetFile
26 |
27 | # 示例
28 | $ scp file.txt remote_username@10.10.0.2:/remote/directory
29 | ```
30 |
31 | 下面是复制整个目录。
32 |
33 | ```bash
34 | # 将本机的 documents 目录拷贝到远程主机,
35 | # 会在远程主机创建 documents 目录
36 | $ scp -r documents username@server_ip:/path_to_remote_directory
37 |
38 | # 将本机整个目录拷贝到远程目录下
39 | $ scp -r localmachine/path_to_the_directory username@server_ip:/path_to_remote_directory/
40 |
41 | # 将本机目录下的所有内容拷贝到远程目录下
42 | $ scp -r localmachine/path_to_the_directory/* username@server_ip:/path_to_remote_directory/
43 | ```
44 |
45 | **(2)远程文件复制到本地**
46 |
47 | 从远程主机复制文件到本地的语法如下。
48 |
49 | ```bash
50 | # 语法
51 | $ scp user@host:directory/SourceFile TargetFile
52 |
53 | # 示例
54 | $ scp remote_username@10.10.0.2:/remote/file.txt /local/directory
55 | ```
56 |
57 | 下面是复制整个目录的例子。
58 |
59 | ```bash
60 | # 拷贝一个远程目录到本机目录下
61 | $ scp -r username@server_ip:/path_to_remote_directory local-machine/path_to_the_directory/
62 |
63 | # 拷贝远程目录下的所有内容,到本机目录下
64 | $ scp -r username@server_ip:/path_to_remote_directory/* local-machine/path_to_the_directory/
65 | $ scp -r user@host:directory/SourceFolder TargetFolder
66 | ```
67 |
68 | **(3)两个远程系统之间的复制**
69 |
70 | 本机发出指令,从远程主机 A 拷贝到远程主机 B 的语法如下。
71 |
72 | ```bash
73 | # 语法
74 | $ scp user@host1:directory/SourceFile user@host2:directory/SourceFile
75 |
76 | # 示例
77 | $ scp user1@host1.com:/files/file.txt user2@host2.com:/files
78 | ```
79 |
80 | 系统将提示您输入两个远程帐户的密码。数据将直接从一个远程主机传输到另一个远程主机。
81 |
82 | ## 参数
83 |
84 | `-P`用来指定远程主机的 SSH 端口。如果远程主机使用非默认端口22,可以在命令中指定。
85 |
86 | ```bash
87 | $ scp -P 2222 user@host:directory/SourceFile TargetFile
88 | ```
89 |
90 | `-p`参数用来保留修改时间(modification time)、访问时间(access time)、文件状态(mode)等原始文件的信息。
91 |
92 | ```bash
93 | $ scp -C -p ~/test.txt root@192.168.1.3:/some/path/test.txt
94 | ```
95 |
96 | `-l`参数用来限制传输数据的带宽速率,单位是 Kbit/sec。对于多人分享的带宽,这个参数可以留出一部分带宽供其他人使用。
97 |
98 | ```bash
99 | $ scp -l 80 yourusername@yourserver:/home/yourusername/* .
100 | ```
101 |
102 | 上面代码中,`scp`命令占用的带宽限制为每秒80K比特位,即每秒10K字节。
103 |
104 | `-c`参数用来指定加密算法。
105 |
106 | ```bash
107 | $ scp -c blowfish some_file your_username@remotehost.edu:~
108 | ```
109 |
110 | 上面代码指定加密算法为`blowfish`。
111 |
112 | `-C`表示是否在传输时压缩文件。
113 |
114 | ```bash
115 | $ scp -c blowfish -C local_file your_username@remotehost.edu:~
116 | ```
117 |
118 | `-q`参数用来关闭显示拷贝的进度条。
119 |
120 | ```bash
121 | $ scp -q Label.pdf mrarianto@202.x.x.x:.
122 | ```
123 |
124 | `-F`参数用来指定 ssh_config 文件。
125 |
126 | ```bash
127 | $ scp -F /home/pungki/proxy_ssh_config Label.pdf
128 | ```
129 |
130 | `-v`参数用来显示详细的输出。
131 |
132 | ```bash
133 | $ scp -v ~/test.txt root@192.168.1.3:/root/help2356.txt
134 | ```
135 |
136 | `-i`参数用来指定密钥。
137 |
138 | ```bash
139 | $ scp -vCq -i private_key.pem ~/test.txt root@192.168.1.3:/some/path/test.txt
140 | ```
141 |
142 | `-r`参数表示是否以递归方式复制目录。
143 |
--------------------------------------------------------------------------------
/docs/archives/commands/sed.md:
--------------------------------------------------------------------------------
1 | # sed
2 |
3 | `sed`命令用于对文本进行过滤和变形处理。
4 |
5 | 下面是`example.txt`文件的内容。
6 |
7 | ```bash
8 | Hello This is a Test 1 2 3 4
9 | replace all spaces with hyphens
10 | ```
11 |
12 | `sed`命令将所有的空格换成连词线`-`。
13 |
14 | ```bash
15 | $ sed 's/ /-/g' example.txt
16 | Hello-This-is-a-Test-1-2-3-4
17 | ```
18 |
19 | 下面的命令将数字换成字母`d`。
20 |
21 | ```bash
22 | $ sed 's/[0-9]/d/g' example.txt
23 | Hello This is a Test d d d d
24 | ```
25 |
26 |
--------------------------------------------------------------------------------
/docs/archives/commands/sort.md:
--------------------------------------------------------------------------------
1 | # sort
2 |
3 | `sort`命令用于文本文件的排序。
4 |
5 | 下面是`example.txt`文件的内容。
6 |
7 | ```bash
8 | f
9 | b
10 | c
11 | g
12 | a
13 | e
14 | d
15 | ```
16 |
17 | 执行`sort`命令对其进行排序。
18 |
19 | ```bash
20 | $ sort example.txt
21 | a
22 | b
23 | c
24 | d
25 | e
26 | f
27 | g
28 | ```
29 |
30 | ## 参数
31 |
32 | `-R`参数表示随机排序。
33 |
34 | ```bash
35 | sort -R example.txt
36 | b
37 | d
38 | a
39 | c
40 | g
41 | e
42 | f
43 | ```
44 |
45 |
--------------------------------------------------------------------------------
/docs/archives/commands/tr.md:
--------------------------------------------------------------------------------
1 | # tr
2 |
3 | `tr`命令用于按照给定模式转换文本。
4 |
5 | 下面是`example.txt`文件的内容。
6 |
7 | ```bash
8 | Hello World Foo Bar Baz!
9 | ```
10 |
11 | `tr`命令可以将所有小写字母转换为大写字母。
12 |
13 | ```bash
14 | $ cat example.txt | tr 'a-z' 'A-Z'
15 | HELLO WORLD FOO BAR BAZ!
16 | ```
17 |
18 | `tr`命令还可以将所有空格转为换行符。
19 |
20 | ```bash
21 | $ cat example.txt | tr ' ' '\n'
22 | Hello
23 | World
24 | Foo
25 | Bar
26 | Baz!
27 | ```
28 |
29 |
--------------------------------------------------------------------------------
/docs/archives/commands/uname.md:
--------------------------------------------------------------------------------
1 | # uname
2 |
3 | `uname`命令用来显示内核信息。
4 |
5 | ```bash
6 | $ uname -a
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/archives/commands/uniq.md:
--------------------------------------------------------------------------------
1 | # uniq
2 |
3 | `uniq`用于过滤掉重复的行,该命令只对排序后的文件有效。
4 |
5 | 下面是`example.txt`文件的内容。
6 |
7 | ```bash
8 | a
9 | a
10 | b
11 | a
12 | b
13 | c
14 | d
15 | c
16 | ```
17 |
18 | 对该文件进行排序后,再过滤掉重复的行。
19 |
20 | ```bash
21 | $ sort example.txt | uniq
22 | a
23 | b
24 | c
25 | d
26 | ```
27 |
28 | ## 参数
29 |
30 | `-c`参数会显示每行一共出现了多少次。
31 |
32 | ```bash
33 | sort example.txt | uniq -c
34 | 3 a
35 | 2 b
36 | 2 c
37 | 1 d
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/docs/archives/commands/uptime.md:
--------------------------------------------------------------------------------
1 | # uptime
2 |
3 | `uptime`命令显示本次开机运行的时间。
4 |
--------------------------------------------------------------------------------
/docs/archives/commands/w.md:
--------------------------------------------------------------------------------
1 | # w
2 |
3 | `w`命令显示当期谁在线。
4 |
--------------------------------------------------------------------------------
/docs/archives/commands/wc.md:
--------------------------------------------------------------------------------
1 | # wc
2 |
3 | `wc`命令返回某个文件的行数、词数和字符数。
4 |
5 | ```bash
6 | $ wc demo.txt
7 | 7459 15915 398400 demo.txt
8 | ```
9 |
10 | 上面代码中,`7459`是行数,`15915`是词数,`398400`是字符数。
11 |
12 |
--------------------------------------------------------------------------------
/docs/archives/commands/whereis.md:
--------------------------------------------------------------------------------
1 | # whereis
2 |
3 | `whereis`用来显示某个命令的位置。如果有多个程序符合条件,会全部列出。
4 |
5 | ```bash
6 | $ whereis node
7 | /usr/bin/node /usr/sbin/node
8 | ```
9 |
10 |
--------------------------------------------------------------------------------
/docs/archives/commands/which.md:
--------------------------------------------------------------------------------
1 | # which
2 |
3 | `which`命令根据`PATH`环境变量指定的顺序,返回最早发现某个命令的位置。即不指定路径时,实际执行的命令的完整路径。
4 |
5 | ```bash
6 | $ which node
7 | /usr/bin/node
8 | ```
9 |
--------------------------------------------------------------------------------
/docs/archives/commands/who.md:
--------------------------------------------------------------------------------
1 | # who
2 |
3 | `who`命令显示已经登录的用户。
4 |
5 | ## 参数
6 |
7 | `-b`参数显示上一次系统启动的时间。
8 |
9 | ```bash
10 | $ who -b
11 | system boot 2017-06-20 17:41
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/archives/deleted/stdio.md:
--------------------------------------------------------------------------------
1 | # 标准I/O
2 |
3 | ## echo
4 |
5 | `echo`命令用于将指定内容输出到显示屏(标准输出)。
6 |
7 | ```bash
8 | $ echo this is a test
9 | this is a test
10 | ```
11 |
12 | 它的参数如下。
13 |
14 | - `-e` 解释转义字符。
15 | - `-n` 不输出行尾的换行符
16 |
17 | ```bash
18 | $ echo "a\nb"
19 | a\nb
20 |
21 | $ echo -e "a\nb"
22 | a
23 | b
24 | ```
25 |
26 | 上面代码中,如果不加`-e`参数,`\n`就会按字面形式输出;加了以后,就被解释成了换行符。
27 |
28 | 引号之中可以包括多个换行符,即可以输出多行文本。
29 |
30 | ```bash
31 | echo "
32 |
33 | Page Title
34 |
35 |
36 | Page body.
37 |
38 | "
39 | ```
40 |
41 | echo '
42 |
43 | Page Title
44 |
45 |
46 | Page body.
47 |
48 | '
49 |
50 | ## read
51 |
52 | `read`命令被用来从标准输入读取单行数据。这个命令可以用来读取键盘输入,当使用重定向的时候,读取文件中的一行数据。
53 |
54 | ```bash
55 | read [-options] [variable...]
56 | ```
57 |
58 | 上面的 variable 用来存储输入数值的一个或多个变量名。如果没有提供变量名,shell 变量`REPLY`会包含数据行。
59 |
60 | 基本上,read 会把来自标准输入的字段赋值给具体的变量。
61 |
62 | ```bash
63 | echo -n "Please enter an integer -> "
64 | read int
65 | ```
66 |
67 | `read`可以给多个变量赋值。
68 |
69 | ```bash
70 | #!/bin/bash
71 | # read-multiple: read multiple values from keyboard
72 | echo -n "Enter one or more values > "
73 | read var1 var2 var3 var4 var5
74 | echo "var1 = '$var1'"
75 | echo "var2 = '$var2'"
76 | echo "var3 = '$var3'"
77 | echo "var4 = '$var4'"
78 | echo "var5 = '$var5'"
79 | ```
80 |
81 | 上面脚本的用法如下。
82 |
83 | ```bash
84 | $ read-multiple
85 | Enter one or more values > a b c d e
86 | var1 = 'a'
87 | var2 = 'b'
88 | var3 = 'c'
89 | var4 = 'd'
90 | var5 = 'e'
91 |
92 | $ read-multiple
93 | Enter one or more values > a
94 | var1 = 'a'
95 | var2 = ''
96 | var3 = ''
97 | var4 = ''
98 | var5 = ''
99 |
100 | $ read-multiple
101 | Enter one or more values > a b c d e f g
102 | var1 = 'a'
103 | var2 = 'b'
104 | var3 = 'c'
105 | var4 = 'd'
106 | var5 = 'e f g'
107 | ```
108 |
109 | 如果 read 命令接受到变量值数目少于期望的数字,那么额外的变量值为空,而多余的输入数据则会 被包含到最后一个变量中。
110 |
111 | 如果 read 命令之后没有列出变量名,则一个 shell 变量`REPLY`,将会包含所有的输入。
112 |
113 | ```bash
114 | #!/bin/bash
115 | # read-single: read multiple values into default variable
116 | echo -n "Enter one or more values > "
117 | read
118 | echo "REPLY = '$REPLY'"
119 | ```
120 |
121 | 上面脚本的输出结果如下。
122 |
123 | ```bash
124 | $ read-single
125 | Enter one or more values > a b c d
126 | REPLY = 'a b c d'
127 | ```
128 |
129 | read命令的参数如下。
130 |
131 | - `-a array` 把输入赋值到数组 array 中,从索引号零开始。
132 | - `-d delimiter` 用字符串 delimiter 中的第一个字符指示输入结束,而不是一个换行符。
133 | - `-e` 使用 Readline 来处理输入。这使得与命令行相同的方式编辑输入。
134 | - `-n num` 读取 num 个输入字符,而不是整行。
135 | - `-p prompt` 为输入显示提示信息,使用字符串 prompt。
136 | - `-r` Raw mode. 不把反斜杠字符解释为转义字符。
137 | - `-s` Silent mode. 不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这会很有帮助。
138 | - `-t seconds` 超时. 几秒钟后终止输入。read 会返回一个非零退出状态,若输入超时。
139 | - `-u fd` 使用文件描述符 fd 中的输入,而不是标准输入。
140 |
141 | `-p`的例子。
142 |
143 | ```bash
144 | read -p "Enter one or more values > "
145 | echo "REPLY = '$REPLY'"
146 | ```
147 |
148 | `-t`和`-s`的例子。
149 |
150 | ```bash
151 | if read -t 10 -sp "Enter secret pass phrase > " secret_pass; then
152 | echo -e "\nSecret pass phrase = '$secret_pass'"
153 | ```
154 |
155 | 上面这个脚本提示用户输入一个密码,并等待输入10秒钟。如果在特定的时间内没有完成输入, 则脚本会退出并返回一个错误。因为包含了一个 -s 选项,所以输入的密码不会出现在屏幕上。
156 |
157 | Shell的内部变量`IFS`可以控制输入字段的分离。例如,这个 /etc/passwd 文件包含的数据行 使用冒号作为字段分隔符。通过把 IFS 的值更改为单个冒号,我们可以使用 read 读取 /etc/passwd 中的内容,并成功地把字段分给不同的变量。
158 |
159 | ```bash
160 | #!/bin/bash
161 | # read-ifs: read fields from a file
162 | FILE=/etc/passwd
163 | read -p "Enter a user name > " user_name
164 | file_info=$(grep "^$user_name:" $FILE)
165 | if [ -n "$file_info" ]; then
166 | IFS=":" read user pw uid gid name home shell <<< "$file_info"
167 | echo "User = '$user'"
168 | echo "UID = '$uid'"
169 | echo "GID = '$gid'"
170 | echo "Full Name = '$name'"
171 | echo "Home Dir. = '$home'"
172 | echo "Shell = '$shell'"
173 | else
174 | echo "No such user '$user_name'" >&2
175 | exit 1
176 | fi
177 | ```
178 |
179 | Shell 允许在一个命令之前立即发生一个或多个变量赋值。这些赋值为跟随着的命令更改环境变量。 这个赋值的影响是暂时的;只是在命令存在期间改变环境变量。
180 |
181 | 虽然通常 read 命令接受标准输入,但是你不能这样做:
182 |
183 | ```bash
184 | $ echo “foo” | read
185 | ```
186 |
187 | 我们期望这个命令能生效,但是它不能。这个命令将显示成功,但是 REPLY 变量 总是为空。为什么会这样?
188 |
189 | 答案与 shell 处理管道线的方式有关系。在 bash(和其它 shells,例如 sh)中,管道线 会创建子 shell。它们是 shell 的副本,且用来执行命令的环境变量在管道线中。 上面示例中,read 命令将在子 shell 中执行。
190 |
191 | 在类 Unix 的系统中,子 shell 执行的时候,会为进程创建父环境的副本。当进程结束 之后,环境副本就会被破坏掉。这意味着一个子 shell 永远不能改变父进程的环境。read 赋值变量, 然后会变为环境的一部分。在上面的例子中,read 在它的子 shell 环境中,把 foo 赋值给变量 REPLY, 但是当命令退出后,子 shell 和它的环境将被破坏掉,这样赋值的影响就会消失。
192 |
193 | 使用 here 字符串是解决此问题的一种方法。
194 |
195 | 下面是生成菜单的一个例子。
196 |
197 | ```bash
198 | #!/bin/bash
199 | # read-menu: a menu driven system information program
200 | clear
201 | echo "
202 | Please Select:
203 |
204 | 1. Display System Information
205 | 2. Display Disk Space
206 | 3. Display Home Space Utilization
207 | 0. Quit
208 | "
209 | read -p "Enter selection [0-3] > "
210 |
211 | if [[ $REPLY =~ ^[0-3]$ ]]; then
212 | if [[ $REPLY == 0 ]]; then
213 | echo "Program terminated."
214 | exit
215 | fi
216 | if [[ $REPLY == 1 ]]; then
217 | echo "Hostname: $HOSTNAME"
218 | uptime
219 | exit
220 | fi
221 | if [[ $REPLY == 2 ]]; then
222 | df -h
223 | exit
224 | fi
225 | if [[ $REPLY == 3 ]]; then
226 | if [[ $(id -u) -eq 0 ]]; then
227 | echo "Home Space Utilization (All Users)"
228 | du -sh /home/*
229 | else
230 | echo "Home Space Utilization ($USER)"
231 | du -sh $HOME
232 | fi
233 | exit
234 | fi
235 | else
236 | echo "Invalid entry." >&2
237 | exit 1
238 | fi
239 | ```
240 |
--------------------------------------------------------------------------------
/docs/archives/file-operation.md:
--------------------------------------------------------------------------------
1 | # 文件操作
2 |
3 | ## cp
4 |
5 | `cp`命令用于将文件(或目录)拷贝到目的地。
6 |
7 | ```bash
8 | # 拷贝单个文件
9 | $ cp source dest
10 |
11 | # 拷贝多个文件
12 | $ cp source1 source2 source3 dest
13 |
14 | # -i 目的地有同名文件时会提示确认
15 | $ cp -i file1 file2
16 |
17 | # -r 递归拷贝,将dir1拷贝到dir2,完成后dir2生成一个子目录dir1
18 | # dir2如果不存在,将被创建
19 | # 拷贝目录时,该参数是必需的
20 | $ cp -r dir1 dir2
21 |
22 | # -u --update 只拷贝目的地没有的文件,或者比目的地同名文件更新的文件
23 | $ cp -u *.html destination
24 | ```
25 |
26 | 其他参数
27 |
28 | - `-a` 拷贝时保留所有属性,包括所有者与权限
29 | - `-v` 显示拷贝的详细信息
30 |
31 | ## mkdir
32 |
33 | `mkdir`命令用于新建目录。
34 |
35 | ```bash
36 | # 新建多个目录
37 | $ mkdir dir1 dir2 dir3
38 | ```
39 |
40 | ## mv
41 |
42 | `mv`命令用于将源文件移动到目的地。
43 |
44 | ```bash
45 | # 移动单个文件
46 | $ mv item1 item2
47 |
48 | # 移动多个文件
49 | $ mv file1 file2 dir1
50 |
51 | # 将dir1拷贝进入dir2,完成后dir2将多出一个子目录dir1
52 | # 如果dir2不存在,将会被创建
53 | $ mv dir1 dir2
54 | ```
55 |
56 | 参数
57 |
58 | - `-i` 覆盖已经存在的文件时,会提示确认
59 | - `-u` 只移动目的地不存在的文件,或比目的地更新的文件
60 |
61 | ## rm
62 |
63 | `rm`命令用于删除文件。
64 |
65 | 参数。
66 |
67 | - `-i` 文件存在时,会提示确认。
68 | - `-r` 递归删除一个子目录
69 | - `-f` 如果删除不存在的文件,不报错
70 | - `-v` 删除时展示详细信息
71 |
72 | ## ln
73 |
74 | `ln`命令用于建立链接文件。
75 |
76 | ```bash
77 | # 新建硬链接
78 | $ ln file link
79 |
80 | # 新建软链接
81 | $ ln -s item link
82 | ```
83 |
84 |
--------------------------------------------------------------------------------
/docs/archives/file.md:
--------------------------------------------------------------------------------
1 | # 文件系统
2 |
3 | ## pwd
4 |
5 | `pwd`命令显示列出当前所在的目录。
6 |
7 | ```bash
8 | $ pwd
9 | ```
10 |
11 | ## cd
12 |
13 | `cd`命令用来改变用户所在的目录。
14 |
15 | ```bash
16 | # 进入用户的主目录
17 | $ cd
18 |
19 | # 进入前一个工作目录
20 | $ cd -
21 |
22 | # 进入指定用户的主目录
23 | $ cd ~user_name
24 | ```
25 |
26 | ## ls
27 |
28 | `ls`目录可以显示指定目录的内容。不加参数时,显示当前目录的内容。
29 |
30 | ```bash
31 | $ ls
32 | ```
33 |
34 | 上面命令显示当前目录的内容。
35 |
36 | `ls`命令也可以显示指定文件是否存在。
37 |
38 | ```bash
39 | $ ls foo.txt
40 | foo.txt
41 | ```
42 |
43 | `-l`参数可以显示文件的详细信息。
44 |
45 | ```bash
46 | $ ls -l foo.txt
47 | -rw-rw-r-- 1 me me 0 2016-03-06 14:52 foo.txt
48 | ```
49 |
50 | 上面命令输出结果的第一栏,是文件的类型和权限。
51 |
52 | 文件类型分为以下几种。
53 |
54 | - `-` 普通文件
55 | - `d` 目录
56 | - `l` 符号链接。注意,对于符号链接文件,剩余的文件属性总是"rwxrwxrwx"。
57 | - `c` 字符设备文件,指按照字节流处理数据的设备,比如调制解调器。
58 | - `b` 块设备文件,指按照数据块处理数据的设备,比如硬盘。
59 |
60 | 其他参数的用法。
61 |
62 | ```bash
63 | # 显示多个目录的内容
64 | $ ls ~ /usr
65 |
66 | # -a --all 显示隐藏文件
67 | $ ls -a
68 |
69 | # -A 与-a类似,但是不显示当前目录和上一级目录两个点文件
70 | $ ls -A
71 |
72 | # -l 显示详细信息
73 | $ ls -l
74 |
75 | # -1 单列显示,每行只显示一个文件
76 | $ ls -1
77 |
78 | # -d 显示当前目录本身,而不是它的内容
79 | # 通常与-l配合使用,列出一个目录本身的详细信息
80 | $ ls -dl
81 |
82 | # -F 目录名之后添加斜杠,可执行文件后面添加星号
83 | $ ls -F
84 |
85 | # -h 与-l配合使用,将文件大小显示为人类可读的格式
86 |
87 | # -t 按文件修改时间排序,修改晚的排在前面
88 | $ ls -t
89 |
90 | # -s 按文件大小排序,
91 |
92 | # --reverse 显示结果倒序排列
93 | $ ls -lt --reverse
94 | ```
95 |
96 | 如果只显示一个目录里面的子目录,不显示文件,可以使用下面这些命令。
97 |
98 | ```bash
99 | # 只显示常规目录
100 | $ ls -d */
101 | $ ls -F | grep /
102 | $ ls -l | grep ^d
103 | $ tree -dL 1
104 |
105 | # 只显示隐藏目录
106 | $ ls -d .*/
107 |
108 | # 隐藏目录和非隐藏目录都显示
109 | $ find -maxdepth 1 -type d
110 | ```
111 |
112 | 另一个简便方法是利用自动补全功能,先键入`cd`命令,然后连按两下`tab`键。
113 |
114 | ## stat
115 |
116 | `stat`命令是加强版的`ls`命令,可以显示一个文件的详细信息。
117 |
118 | ```bash
119 | $ stat timestamp
120 | File: 'timestamp'
121 | Size: 0 Blocks: 0 IO Block: 4096 regular empty file
122 | Device: 803h/2051d Inode: 14265061 Links: 1
123 | Access: (0644/-rw-r--r--) Uid: ( 1001/ me) Gid: ( 1001/ me)
124 | Access: 2008-10-08 15:15:39.000000000 -0400
125 | Modify: 2008-10-08 15:15:39.000000000 -0400
126 | Change: 2008-10-08 15:15:39.000000000 -0400
127 | ```
128 |
129 | ## touch
130 |
131 | `touch`用来设置或更新文件的访问,更改,和修改时间。然而,如果一个文件名参数是一个 不存在的文件,则会创建一个空文件。
132 |
133 | ```bash
134 | $ touch timestamp
135 | ```
136 |
137 | 上面命令创建了一个名为`timestamp`空文件。如果该文件已经存在,就会把它的修改时间设置为当前时间。
138 |
139 | ```bash
140 | $ mkdir -p playground/dir-{00{1..9},0{10..99},100}
141 | $ touch playground/dir-{00{1..9},0{10..99},100}/file-{A..Z}
142 | ```
143 |
144 | 上面的命令创建了一个包含一百个子目录,每个子目录中包含了26个空文件。
145 |
146 | ## file
147 |
148 | `file`命令显示指定文件的类型。
149 |
150 | ```bash
151 | $ file picture.jpg
152 | picture.jpg: JPEG image data, JFIF standard 1.01
153 | ```
154 |
155 | ## chmod
156 |
157 | `chmod`命令用于更改文件的权限,是“change mode”的缩写。
158 |
159 | ```bash
160 | $ chmod 600 foo.txt
161 | ```
162 |
163 | 上面命令将`foo.txt`的权限改成了600。
164 |
165 | `chmod`还可以接受四个缩写,为不同的对象单独设置权限。
166 |
167 | - `u` 所有者“user”的简写
168 | - `g` 用户组“group”的缩写
169 | - `o` 其他所有人“others”的简写
170 | - `a` 所有人“all”的简写
171 |
172 | ```bash
173 | # 为所有者添加可执行权限
174 | $ chmod u+x foo.txt
175 |
176 | # 删除所有者的可执行权限
177 | $ chmod u-x foo.txt
178 |
179 | # 为所有人添加可执行权限,等价于 a+x
180 | $ chmod +x foo.txt
181 |
182 | # 删除其他人的读权限和写权限。
183 | $ chmod o-rw foo.txt
184 |
185 | # 设定用户组和其他人的权限是读权限和写权限
186 | $ chmod go=rw foo.txt
187 |
188 | # 为所有者添加执行权限,设定用户组和其他人为读权限和写权限,多种设定用逗号分隔
189 | $ chmod u+x,go=rw foo.txt
190 | ```
191 |
192 | 添加权限。
193 |
194 | - +x 添加执行权限
195 | - +r 设置读权限
196 | - +w 设置写权限
197 | - +rwx 设置所有读、写和执行权限。
198 |
199 | 删除权限只需将`+`更改为`-`,就可以删除任何已设置的指定权限。可以使用`-R`(或`--recursive`)选项来递归地操作目录和文件。
200 |
201 | 设置精确权限,可以使用`=`代替`+`或`-`来实现此操作。如果想为用户、组或其他用户设置不同的权限,可以使用逗号将不同表达式分开(例如`ug=rwx,o=rx`)。
202 |
203 | 由于一共有3种可能的权限。也可以使用八进制数代替符号来设置权限。通过这种方式设置的权限最多使用3个八进制数。第1个数定义用户权限,第2个数定义组权限,第3个数定义其他权限。这3个数中的每一个都通过添加想要的权限设置来构造:读 (4)、写 (2) 和执行 (1)。
204 |
205 | - rwx 7
206 | - rw- 6
207 | - r-x 5
208 | - r-- 4
209 | - -wx 3
210 | - -w- 2
211 | - --x 1
212 | - --- 0
213 |
214 | ## umask
215 |
216 | `umask`用来查看和设置权限掩码。
217 |
218 | ```bash
219 | $ umask
220 | 0022
221 | ```
222 |
223 | 上面命令显示当前系统之中,默认的文件掩码是`0022`,转为二进制就是`000 000 010 010`。
224 |
225 | 可以看到,这个掩码是一个12位的二进制数,后面的9位分别代表文件三种使用对象的三类权限。只要对应位置上是`1`,就表示关闭该项权限,所以`010`就表示关闭读权限。
226 |
227 | 新建文件时,通常不会带有执行权限,也就是说,新建文件的默认权限是`rw-rw-rw-`。如果文件掩码是`0022`,那么用户组和其他人的写权限也会被拿掉。
228 |
229 | ```bash
230 | $ touch new.txt
231 | $ ls -l new.txt
232 | -rw-r--r-- 1 me me 0 2016-03-06 14:52 new.txt
233 | ```
234 |
235 | 上面代码中,`new.txt`的用户组和其他人的写权限就没了。
236 |
237 | `umask`后面跟着参数,就表示设置权限掩码。
238 |
239 | ```bash
240 | $ umask 0000
241 | ```
242 |
243 | 上面命令将权限掩码设为`0000`,实际上就是关闭了权限掩码。
244 |
245 | `umask`命令设置的掩码值只能在当前Shell会话中生效,若当前Shell会话结束后,则必须重新设置。
246 |
247 | ## du
248 |
249 | `du`命令用于查看指定目录的大小。
250 |
251 | ```bash
252 | $ du -hs /path/to/directory
253 | ```
254 |
255 | 显示第一层子目录的大小。
256 |
257 | ```bash
258 | $ du -h --max-depth=1 /path/to/folder
259 | ```
260 |
261 | 参数的含义。
262 |
263 | - `-h` 表示人类可读的格式
264 | - `-s` 表示总结信息,否则会显示该目录内所有文件和子目录的信息。
265 |
266 | `tree`命令也可以显示子目录大小。
267 |
268 | ```bash
269 | $ tree --du -h /path/to/directory
270 | ```
271 |
272 | ## md5sum
273 |
274 | `md5sum`命令用来显示一个文件的md5校验码。
275 |
276 | ```bash
277 | $ md5sum image.iso
278 | 34e354760f9bb7fbf85c96f6a3f94ece image.iso
279 | ```
280 |
281 | ## locate
282 |
283 | `locate`程序快速搜索本机的路径名数据库,并且输出每个与给定字符串相匹配的文件名。
284 |
285 | ```bash
286 | $ locate bin/zip
287 | /usr/bin/zip
288 | /usr/bin/zipcloak
289 | /usr/bin/zipgrep
290 | /usr/bin/zipinfo
291 | /usr/bin/zipnote
292 | /usr/bin/zipsplit
293 | ```
294 |
295 | `locate`数据库由另一个叫做`updatedb`的程序创建。大多数装有 locate 的系统会每隔一天运行一回 updatedb 程序。因为数据库不能被持续地更新,所以当使用 locate 时,你会发现 目前最新的文件不会出现。为了克服这个问题,可以手动运行 updatedb 程序, 更改为超级用户身份,在提示符下运行 updatedb 命令。
296 |
297 | `locate`支持正则查找。`--regexp`参数支持基本的正则表达式,`--regex`参数支持扩展的正则表达式。
298 |
299 | ```bash
300 | $ locate --regex 'bin/(bz|gz|zip)'
301 | ```
302 |
303 | ## find
304 |
305 | `locate`程序只能依据文件名来查找文件,而`find`程序能基于各种各样的属性,搜索一个给定目录(以及它的子目录),来查找文件。
306 |
307 | ```bash
308 | # 输出当前目录的所有子目录和文件(含子目录)
309 | $ find
310 | $ find .
311 |
312 | # 显示当前目录的文件总数
313 | $ find . | wc -l
314 |
315 | # 当前目录的子目录总数
316 | $ find . -type d | wc -l
317 |
318 | # 当前目录的文件总数(不含子目录)
319 | $ find . -type f | wc -l
320 |
321 | # 当前目录的文件名匹配“*.JPG”且大于1M的文件总数
322 | $ find . -type f -name "\*.JPG" -size +1M | wc -l
323 | ```
324 |
325 | `-type`参数支持的文件类型。
326 |
327 | - `b` 块设备文件
328 | - `c` 字符设备文件
329 | - `d` 目录
330 | - `f` 普通文件
331 | - `l` 符号链接
332 |
333 | `-size`参数支持的文件大小类型。
334 |
335 | - b 512 个字节块。如果没有指定单位,则这是默认值。
336 | - c 字节
337 | - w 两个字节的字
338 | - k 千字节
339 | - M 兆字节
340 | - G 千兆字节
341 |
342 | `find`程序支持的查询参数。
343 |
344 | - -cmin n 匹配的文件和目录的内容或属性最后修改时间正好在 n 分钟之前。 指定少于 n 分钟之前,使用 -n,指定多于 n 分钟之前,使用 +n。
345 | - -cnewer file 匹配的文件和目录的内容或属性最后修改时间早于那些文件。
346 | - -ctime n 匹配的文件和目录的内容和属性最后修改时间在 n\*24小时之前。
347 | - -empty 匹配空文件和目录。
348 | - -group name 匹配的文件和目录属于一个组。组可以用组名或组 ID 来表示。
349 | - -iname pattern 就像-name 测试条件,但是不区分大小写。
350 | - -inum n 匹配的文件的 inode 号是 n。这对于找到某个特殊 inode 的所有硬链接很有帮助。
351 | - -mmin n 匹配的文件或目录的内容被修改于 n 分钟之前。
352 | - -mtime n 匹配的文件或目录的内容被修改于 n\*24小时之前。
353 | - -name pattern 用指定的通配符模式匹配的文件和目录。
354 | - -newer file 匹配的文件和目录的内容早于指定的文件。当编写 shell 脚本,做文件备份时,非常有帮助。 每次你制作一个备份,更新文件(比如说日志),然后使用 find 命令来决定自从上次更新,哪一个文件已经更改了。
355 | - -nouser 匹配的文件和目录不属于一个有效用户。这可以用来查找 属于删除帐户的文件或监测攻击行为。
356 | - -nogroup 匹配的文件和目录不属于一个有效的组。
357 | - -perm mode 匹配的文件和目录的权限已经设置为指定的 mode。mode 可以用 八进制或符号表示法。
358 | - -samefile name 相似于-inum 测试条件。匹配和文件 name 享有同样 inode 号的文件。
359 | - -size n 匹配的文件大小为 n。
360 | - -type c 匹配的文件类型是 c。
361 | - -user name 匹配的文件或目录属于某个用户。这个用户可以通过用户名或用户 ID 来表示。
362 | - -depth 指导 find 程序先处理目录中的文件,再处理目录自身。当指定-delete 行为时,会自动 应用这个选项。
363 | - -maxdepth levels 当执行测试条件和行为的时候,设置 find 程序陷入目录树的最大级别数
364 | - -mindepth levels 在应用测试条件和行为之前,设置 find 程序陷入目录数的最小级别数。
365 | - -mount 指导 find 程序不要搜索挂载到其它文件系统上的目录。
366 | - -regex 指定正则表达式
367 |
368 | ```bash
369 | # 找出包括空格或其它不规范字符的文件名或路径名
370 | $ find . -regex '.*[^-\_./0-9a-zA-Z].*'
371 | ```
372 |
373 | `find`程序还支持逻辑操作符。
374 |
375 | - `-and` 如果操作符两边的测试条件都是真,则匹配。可以简写为 -a。 注意若没有使用操作符,则默认使用 -and。
376 | - `-or` 若操作符两边的任一个测试条件为真,则匹配。可以简写为 -o。
377 | - `-not` 若操作符后面的测试条件是真,则匹配。可以简写为一个感叹号(!)。
378 | - `()` 把测试条件和操作符组合起来形成更大的表达式。这用来控制逻辑计算的优先级。注意 因为圆括号字符对于 shell 来说有特殊含义,所以在命令行中使用它们的时候,它们必须 用引号引起来,才能作为实参传递给 find 命令。通常反斜杠字符被用来转义圆括号字符。
379 |
380 | ```bash
381 | # 或关系
382 | ( expression 1 ) -or ( expression 2 )
383 |
384 | # 找出不是600权限的文件,或者不是700权限的目录
385 | $ find ~ \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)
386 | ```
387 |
388 | `find`程序的逻辑表达式,具有“短路运算”的特点,即对于`expr1 -operator expr2`这个表达式,`expr2`不一定执行。这是为了提高运行速度。
389 |
390 | - expr1 为真,且操作符为`-and`,expr2 总是执行
391 | - expr1 为假,且操作符为`-and`,expr2 从不执行
392 | - expr1 为真,且操作符为`-or`,expr2 从不执行
393 | - expr1 为假,且操作符为`-or`,expr2 总是执行
394 |
395 | 为了方便执行一些常见操作,`find`程序定义了一些预定义操作。
396 |
397 | - -delete 删除当前匹配的文件。
398 | - -ls 对匹配的文件执行等同的 ls -dils 命令。并将结果发送到标准输出。
399 | - -print 把匹配文件的全路径名输送到标准输出。如果没有指定其它操作,这是 默认操作。
400 | - -quit 一旦找到一个匹配,退出。
401 |
402 | ```bash
403 | # 找到匹配的文件,并显示在标准输出
404 | # -print 是默认操作,可以省略
405 | $ find . -print
406 |
407 | # 删除后缀名为BAK的文件
408 | # 执行 delete 操作前,最好先执行 print 操作,确认要删除哪些文件
409 | $ find . -type f -name '*.BAK' -delete
410 | ```
411 |
412 | 预定义操作可以与逻辑表达式,结合使用。
413 |
414 | ```bash
415 | $ find ~ -type f -and -name '*.BAK' -and -print
416 | ```
417 |
418 | 除了预定义操作以外,用户还可以使用`-exec`参数自定义操作。
419 |
420 | ```bash
421 | -exec command {} ;
422 | ```
423 |
424 | 上面的命令中,`command`是一个命令行命令,`{}`用来指代当前路径,分号表示命令结束。
425 |
426 | ```bash
427 | # 预定义的 -delete 操作,等同于下面的操作
428 | -exec rm '{}' ';'
429 | ```
430 |
431 | `-exec`使用时,每次找到一个匹配的文件,会启动一个新的指定命令的实例。
432 |
433 | ```bash
434 | $ find ~ -type f -name 'foo*' -exec ls -l '{}' ';'
435 | ```
436 |
437 | 执行上面的命令,`ls`程序可能会被调用多次。
438 |
439 | ```bash
440 | $ ls -l file1
441 | $ ls -l file2
442 | ```
443 |
444 | 如果想改成`ls`程序只调用一次,要把`find`命令里面的分号,改成加号。
445 |
446 | ```bash
447 | $ ls -l file1 file2
448 | # 相当于
449 | $ find ~ -type f -name 'foo*' -exec ls -l '{}' +
450 | ```
451 |
452 | ## xargs
453 |
454 | `xargs`命令从标准输入接受输入,并把输入转换为一个特定命令的参数列表。
455 |
456 | ```bash
457 | $ find ~ -type f -name 'foo\*' -print | xargs ls -l
458 | ```
459 |
--------------------------------------------------------------------------------
/docs/archives/hardware.md:
--------------------------------------------------------------------------------
1 | # 硬件操作
2 |
3 | ## df
4 |
5 | `df`命令查看硬盘信息。
6 |
7 | ```bash
8 | $ df
9 | Filesystem 1K-blocks Used Available Use% Mounted on
10 | /dev/sda2 15115452 5012392 9949716 34% /
11 | /dev/sda5 59631908 26545424 30008432 47% /home
12 | /dev/sda1 147764 17370 122765 13% /boot
13 | ```
14 |
15 | ## free
16 |
17 | `free`命令查看内存占用情况。
18 |
19 | ```bash
20 | $ free
21 | total used free shared buffers cached
22 | Mem: 513712 503976 9736 0 5312 122916
23 | -/+ buffers/cache: 375748 137964
24 | Swap: 1052248 104712 947536
25 | ```
26 |
27 | ## 硬盘
28 |
29 | 文件`/etc/fstab`配置系统启动时要挂载的设备。
30 |
31 | ```
32 | LABEL=/12 / ext3 defaults 1 1
33 | LABEL=/home /home ext3 defaults 1 2
34 | LABEL=/boot /boot ext3 defaults 1 2
35 | ```
36 |
37 | 输出结果一共有6个字段,含义依次如下。
38 |
39 | - 设备名:与物理设备相关联的设备文件(或设备标签)的名字,比如说`/dev/hda1`(第一个 IDE 通道上第一个主设备分区)。
40 | - 挂载点:设备所连接到的文件系统树的目录。
41 | - 文件系统类型:Linux 允许挂载许多文件系统类型。
42 | - 选项:文件系统可以通过各种各样的选项来挂载。
43 | - 频率:一位数字,指定是否和在什么时间用 dump 命令来备份一个文件系统。
44 | - 次序:一位数字,指定 fsck 命令按照什么次序来检查文件系统。
45 |
46 | ## mount
47 |
48 | `mount`不带参数时,显示当前挂载的文件系统。
49 |
50 | ```bash
51 | $ mount
52 | /dev/sda2 on / type ext3 (rw)
53 | proc on /proc type proc (rw)
54 | sysfs on /sys type sysfs (rw)
55 | devpts on /dev/pts type devpts (rw,gid=5,mode=620)
56 | /dev/sda5 on /home type ext3 (rw)
57 | ```
58 |
59 | 这个列表的格式是:设备 on 挂载点 type 文件系统类型(可选的)。
60 |
61 | `mount`带参数时,用于将设备文件挂载到挂载点,`-t`参数用来指定文件系统类型。
62 |
63 | ```bash
64 | $ mount -t iso9660 /dev/hdc /mnt/cdrom
65 |
66 | # 挂载一个iso文件
67 | $ mount -t iso9660 -o loop image.iso /mnt/iso_image
68 | ```
69 |
70 | ## umount
71 |
72 | `umount`命令用来卸载设备。
73 |
74 | ```bash
75 | $ umount [设备名]
76 |
77 | $ umount /dev/hdc
78 | ```
79 |
80 | ## fdisk
81 |
82 | `fdisk`命令用于格式化磁盘。
83 |
84 | ```bash
85 | $ sudo umount /dev/sdb1
86 | $ sudo fdisk /dev/sdb
87 | ```
88 |
89 | ## mkfs
90 |
91 | `mkfs`命令用于在一个设备上新建文件系统。
92 |
93 | ```bash
94 | $ sudo mkfs -t ext3 /dev/sdb1
95 | $ sudo mkfs -t vfat /dev/sdb1
96 | ```
97 |
98 | ## fsck
99 |
100 | `fsck`命令用于检查(修复)文件系统。
101 |
102 | ```bash
103 | $ sudo fsck /dev/sdb1
104 | ```
105 |
106 | ## dd
107 |
108 | `dd`命令用于将大型数据块,从一个磁盘复制到另一个磁盘。
109 |
110 | ```bash
111 | $ dd if=input_file of=output_file [bs=block_size [count=blocks]]
112 |
113 | # 将 /dev/sdb 的所有数据复制到 /dev/sdc
114 | $ dd if=/dev/sdb of=/dev/sdc
115 |
116 | # 将 /dev/sdb 的所有数据拷贝到一个镜像文件
117 | $ dd if=/dev/sdb of=flash_drive.img
118 |
119 | # 从cdrom制作一个iso文件
120 | $ dd if=/dev/cdrom of=ubuntu.iso
121 | ```
122 |
123 | ## dmidecode
124 |
125 | `dmidecode`命令用于输出BIOS信息。
126 |
127 | ```bash
128 | $ sudo dmidecode
129 | ```
130 |
131 | 以上命令会输出全部BIOS信息。为了便于查看,往往需要指定所需信息的类别。
132 |
133 | - 0 BIOS
134 | - 1 System
135 | - 2 Base Board
136 | - 3 Chassis 4 Processor
137 | - 5 Memory Controller
138 | - 6 Memory Module
139 | - 7 Cache
140 | - 8 Port Connector
141 | - 9 System Slots
142 | - 10 On Board Devices
143 | - 11 OEM Strings
144 | - 12 System Configuration Options
145 | - 13 BIOS Language
146 | - 14 Group Associations
147 | - 15 System Event Log
148 | - 16 Physical Memory Array
149 | - 17 Memory Device
150 | - 18 32-bit Memory Error
151 | - 19 Memory Array Mapped Address
152 | - 20 Memory Device Mapped Address
153 | - 21 Built-in Pointing Device
154 | - 22 Portable Battery
155 | - 23 System Reset
156 | - 24 Hardware Security
157 | - 25 System Power Controls
158 | - 26 Voltage Probe
159 | - 27 Cooling Device
160 | - 28 Temperature Probe
161 | - 29 Electrical Current Probe
162 | - 30 Out-of-band Remote Access
163 | - 31 Boot Integrity Services
164 | - 32 System Boot
165 | - 33 64-bit Memory Error
166 | - 34 Management Device
167 | - 35 Management Device Component
168 | - 36 Management Device Threshold Data
169 | - 37 Memory Channel
170 | - 38 IPMI Device
171 | - 39 Power Supply
172 |
173 | 查看内存信息的命令如下。
174 |
175 | ```bash
176 | $ sudo dmidecode -t 17
177 | # 或者
178 | $ dmidecode --type 17
179 | ```
180 |
181 | 以下是其他一些选项。
182 |
183 | ```bash
184 | # 查看BIOS信息
185 | $ sudo dmidecode –t 0
186 |
187 | # 查看CPU信息
188 | $ sudo dmidecode -t 4
189 | ```
190 |
191 | `dmidecode`也支持关键词查看,关键词与类别的对应关系如下。
192 |
193 | - bios 0, 13
194 | - system 1, 12, 15, 23, 32
195 | - baseboard 2, 10
196 | - chassis 3
197 | - processor 4
198 | - memory 5, 6, 16, 17
199 | - cache 7
200 | - connector 8
201 | - slot 9
202 |
203 | 查看系统信息的命令如下。
204 |
205 | ```bash
206 | $ sudo dmidecode -t system
207 | ```
208 |
209 | ## lspci
210 |
211 | `lspci`命令列出本机的所有PCI设备。
212 |
213 | ```bash
214 | $ lspci
215 | ```
216 |
217 | 该命令输出信息的格式如下。
218 |
219 | ```bash
220 | 03:00.0 Unassigned class [ff00]: Realtek Semiconductor Co., Ltd. RTS5209 PCI Express Card Reader (rev 01)
221 | ```
222 |
223 | 输出信息一共分成三个字段。
224 |
225 | - Field 1:PCI bus slot 的编号
226 | - Field 2:PCI slot的名字
227 | - Field 3:设备名和厂商名
228 |
229 | 如果想查看更详细信息,可以使用下面的命令。
230 |
231 | ```bash
232 | $ lspci -vmm
233 | ```
234 |
235 | ## lsusb
236 |
237 | `lsusb`命令用于操作USB端口。
238 |
239 | 下面命令列出本机所有USB端口。
240 |
241 | ```bash
242 | $ lsusb
243 | ```
244 |
245 | 它的输出格式如下。
246 |
247 | ```bash
248 | Bus 002 Device 003: ID 0781:5567 SanDisk Corp. Cruzer Blade
249 | ```
250 |
251 | 各个字段的含义如下。
252 |
253 | - Bus 002 : bus编号
254 | - Device 003:bus 002连接的第三个设备
255 | - ID 0781:5567:当前设备的编号,冒号前是厂商编号,冒号后是设备编号
256 | - SanDisk Corp. Cruzer Blade:厂商和设备名
257 |
258 | 找出本机有多少个USB接口可用。
259 |
260 | ```bash
261 | $ find /dev/bus/
262 | /dev/bus/
263 | /dev/bus/usb
264 | /dev/bus/usb/002
265 | /dev/bus/usb/002/006
266 | /dev/bus/usb/002/005
267 | /dev/bus/usb/002/004
268 | /dev/bus/usb/002/002
269 | /dev/bus/usb/002/001
270 | /dev/bus/usb/001
271 | /dev/bus/usb/001/007
272 | /dev/bus/usb/001/003
273 | /dev/bus/usb/001/002
274 | /dev/bus/usb/001/001
275 | ```
276 |
277 | 查看某个USB设备的详细情况。
278 |
279 | ```bash
280 | $ lsusb -D /dev/bus/usb/002/005
281 | ```
282 |
283 | 查看所有设备的详细情况。
284 |
285 | ```bash
286 | $ lsusb -v
287 | ```
288 |
289 | 查看USB端口的版本。
290 |
291 | ```bash
292 | $ lsusb -v | grep -i bcdusb
293 | ```
294 |
--------------------------------------------------------------------------------
/docs/archives/host.md:
--------------------------------------------------------------------------------
1 | # 主机管理
2 |
3 | ## hostname命令
4 |
5 | `hostname`命令返回当前服务器的主机名。
6 |
7 | ```bash
8 | $ hostname
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/archives/named-pipe.md:
--------------------------------------------------------------------------------
1 | # 命名管道
2 |
3 | 在大多数类似 Unix 的操作系统中,有可能创建一种特殊类型的文件,叫做命名管道。命名管道用来在 两个进程之间建立连接,也可以像其它类型的文件一样使用。
4 |
5 | 命令管道的行为类似于文件,但实际上形成了先入先出(FIFO)的缓冲。和普通(未命令的)管道一样, 数据从一端进入,然后从另一端出现。通过命令管道,有可能像这样设置一些东西:
6 |
7 | ```bash
8 | process1 > named_pipe
9 | ```
10 |
11 | 和
12 |
13 | ```bash
14 | process2 < named_pipe
15 | ```
16 |
17 | 表现出来就像这样:
18 |
19 | ```bash
20 | process1 | process2
21 | ```
22 |
23 | ## 设置一个命名管道
24 |
25 | 使用 mkfifo 命令能够创建命令管道:
26 |
27 | ```bash
28 | $ mkfifo pipe1
29 | $ ls -l pipe1
30 | prw-r--r-- 1 me me 0 2009-07-17 06:41 pipe1
31 | ```
32 |
33 | 这里我们使用 mkfifo 创建了一个名为 pipe1 的命名管道。使用 ls 命令,我们查看这个文件, 看到位于属性字段的第一个字母是 “p”,表明它是一个命名管道。
34 |
35 | ## 使用命名管道
36 |
37 | 为了演示命名管道是如何工作的,我们将需要两个终端窗口(或用两个虚拟控制台代替)。 在第一个终端中,我们输入一个简单命令,并把命令的输出重定向到命名管道:
38 |
39 | ```bash
40 | $ ls -l > pipe1
41 | ```
42 |
43 | 我们按下 Enter 按键之后,命令将会挂起。这是因为在管道的另一端没有任何接受数据。当这种现象发生的时候, 据说是管道阻塞了。一旦我们绑定一个进程到管道的另一端,该进程开始从管道中读取输入的时候,这种情况会消失。 使用第二个终端窗口,我们输入这个命令。
44 |
45 | ```bash
46 | $ cat < pipe1
47 | ```
48 |
49 | 然后产自第一个终端窗口的目录列表出现在第二个终端中,并作为来自 cat 命令的输出。在第一个终端 窗口中的 ls 命令一旦它不再阻塞,会成功地结束。
50 |
51 |
52 |
--------------------------------------------------------------------------------
/docs/archives/process.md:
--------------------------------------------------------------------------------
1 | # 进程管理
2 |
3 | ## ps
4 |
5 | `ps`命令用来列出进程信息。
6 |
7 | ```bash
8 | $ ps
9 | PID TTY TIME CMD
10 | 5198 pts/1 00:00:00 bash
11 | 10129 pts/1 00:00:00 ps
12 | ```
13 |
14 | 不带任何参数时,`ps`只列出与当前Session相关的进程。输出结果中,`PID`是进程ID、`TTY`是进程的终端号(如果显示`?`,则表示进程没有终端),`TIME`是消耗的CPU时间,`CMD`是触发进程的命令。
15 |
16 | `x`参数列出所有进程的详细信息,包括不在当前Session的信息。
17 |
18 | ```bash
19 | $ ps x
20 | PID TTY STAT TIME COMMAND
21 | 2799 ? Ssl 0:00 /usr/libexec/bonobo-activation-server –ac
22 | 2820 ? Sl 0:01 /usr/libexec/evolution-data-server-1.10 --
23 | ```
24 |
25 | 这时的输出结果,会多出`STAT`一栏,表示状态。它的各种值如下。
26 |
27 | - `R` 正在运行或准备运行
28 | - `S` 正在睡眠,即没有运行,正在等待一个事件唤醒
29 | - `D` 不可中断睡眠。进程正在等待 I/O,比如磁盘驱动器的I/O
30 | - `T` 已停止,即进程停止运行
31 | - `Z` “僵尸”进程。即这是一个已经终止的子进程,但父进程还没有清空它(没有把子进程从进程表中删除)
32 | - `<` 高优先级进程。这可能会授予一个进程更多重要的资源,给它更多的 CPU 时间。
33 | - `N` 低优先级进程。一个低优先级进程(一个“好”进程)只有当其它高优先级进程执行之后,才会得到处理器时间。
34 |
35 | `aux`参数可以显示更多信息。
36 |
37 | ```bash
38 | $ ps aux
39 | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
40 | root 1 0.0 0.0 2136 644 ? Ss Mar05 0:31 init
41 | root 2 0.0 0.0 0 0 ? S< Mar05 0:00 [kt]
42 | ```
43 |
44 | 输出结果包含的列的含义如下。
45 |
46 | - `USER` 用户ID,表示进程的所有者
47 | - `%CPU` 百分比表示的 CPU 使用率
48 | - `%MEM` 百分比表示的内存使用率
49 | - `VSZ` 虚拟内存大小
50 | - `RSS` 进程占用的物理内存的大小,以千字节为单位。
51 | - `START` 进程运行的起始时间。若超过24小时,则用天表示。
52 |
53 | ## top
54 |
55 | `top`命令可以查看机器的当前状态。
56 |
57 | ```bash
58 | $ top
59 | ```
60 |
61 | 它的输出结果分为两部分,最上面是系统概要,下面是进程列表,以 CPU 的使用率排序。
62 |
63 | 输出结果是动态更新的,默认每三分钟更新一次。
64 |
65 | ## jobs
66 |
67 | `jobs`命令用来查看后台任务。
68 |
69 | ```bash
70 | $ jobs
71 | [1]+ Running xlogo &
72 | ```
73 |
74 | 输出结果之中,每个后台任务会有一个编号。上面结果中,`xlogo`的编号是`1`,`+`表示正在运行。
75 |
76 | ## fg
77 |
78 | `fg`命令用于将后台任务切换到前台。
79 |
80 | ```bash
81 | $ fg %1
82 | ```
83 |
84 | `fg`命令之后,跟随着一个百分号和工作序号,用来指定切换哪一个后台任务。如果只有一个后台任务,那么`fg`命令可以不带参数。
85 |
86 | ## bg
87 |
88 | `bg`命令用于将一个暂停的前台任务,转移到后台。只有暂停的任务,才能使用`bg`命令,因为正在运行的任务,命令行是无法输入的。
89 |
90 | ```bash
91 | $ bg %1
92 | ```
93 |
94 | `Ctrl + z`可以暂停正在运行的前台任务。
95 |
96 | ## kill
97 |
98 | `kill`命令用于杀死进程。它的参数是进程ID。
99 |
100 | ```bash
101 | $ kill 28401
102 | ```
103 |
104 | `kill`命令的实质是操作系统向进程发送信号。在使用 Ctrl-c 的情况下,会发送一个叫做 INT(中断)的信号;当使用 Ctrl-z 时,则发送一个叫做 TSTP(终端停止)的信号。
105 |
106 | `kill`命令可以用来向进程发送指定信号。
107 |
108 | ```bash
109 | $ kill [-signal] PID
110 | ```
111 |
112 | 下面是常见信号。
113 |
114 | - HUP:编号1,表示挂起。发送这个信号到前台程序,程序会终止。许多守护进程也使用这个信号,来重新初始化。这意味着,当发送这个信号到一个守护进程后, 这个进程会重新启动,并且重新读取它的配置文件。Apache 网络服务器守护进程就是一个例子。
115 | - INT:编号2,中断。实现和`Ctrl-c`一样的功能,由终端发送。通常,它会终止一个程序。
116 | - KILL:编号9,杀死。进程可能选择忽略这个信号。所以,操作系统不发送该信号到目标进程,而是内核立即终止这个进程。当一个进程以这种方式终止的时候,它没有机会去做些“清理”工作,或者是保存劳动成果。因为这个原因,把 KILL 信号看作杀手锏,当其它终止信号失败后,再使用它。
117 | - TERM:编号15,终止。这是 kill 命令发送的默认信号。如果程序仍然“活着”,可以接受信号,那么这个信号终止。
118 | - CONT:编号18,继续。在停止一段时间后,进程恢复运行。
119 | - STOP:编号19,停止。这个信号导致进程停止运行,而没有终止。像 KILL 信号,它不被 发送到目标进程,因此它不能被忽略。
120 | - QUIT:编号3,退出
121 | - SEGV:编号11,段错误。如果一个程序非法使用内存,就会发送这个信号。也就是说,程序试图写入内存,而这个内存空间是不允许此程序写入的。
122 | - TSTP:编号20,终端停止。当按下 Ctrl-z 组合键后,终端发送这个信号。不像 STOP 信号, TSTP 信号由目标进程接收,且可能被忽略。
123 | - WINCH:编号28,改变窗口大小。当改变窗口大小时,系统会发送这个信号。 一些程序,像 top 和 less 程序会响应这个信号,按照新窗口的尺寸,刷新显示的内容。
124 |
125 | `-l`参数可以列出所有信号。
126 |
127 | ```bash
128 | $ kill -l
129 | ```
130 |
131 | ## killall
132 |
133 | `killall`命令用于向指定的程序或用户发送信号。
134 |
135 | ```bash
136 | $ killall [-u user] [-signal] name
137 | ```
138 |
139 | ## 其他进程相关命令
140 |
141 | - `pstree` 输出树型结构的进程列表,这个列表展示了进程间父/子关系。
142 | - `vmstat` 输出一个系统资源使用快照,包括内存,交换分区和磁盘 I/O。 为了看到连续的显示结果,则在命令名后加上延时的时间(以秒为单位)。例如,“vmstat 5”。 终止输出,按下 Ctrl-c 组合键。
143 | - `xload` 一个图形界面程序,可以画出系统负载的图形。
144 | - `tload` 与`xload`程序相似,但是在终端中画出图形。使用 Ctrl-c,来终止输出。
145 |
--------------------------------------------------------------------------------
/docs/archives/redirection.md:
--------------------------------------------------------------------------------
1 | # 重定向
2 |
3 | 重定向指的是将命令行输出写入指定位置。
4 |
5 | - `cmd1 | cmd2`:Pipe; take standard output of cmd1 as standard input to cmd2.
6 | - `> file`:Direct standard output to file.
7 | - `< file`:Take standard input from file.
8 | - `>> file`:Direct standard output to file; append to file if it already exists.
9 | - `>| file`:Force standard output to file even if noclobber is set.
10 | - `n>| file`:Force output to file from file descriptor n even if noclobber is set.
11 | - `<> file`:Use file as both standard input and standard output.
12 | - `n<> file`:Use file as both input and output for file descriptor n.
13 | - `<< label`:Here-document; see text.
14 | - `n > file`:Direct file descriptor n to file.
15 | - `n < file`:Take file descriptor n from file.
16 | - `n >> file`:Direct file descriptor n to file; append to file if it already exists.
17 | - `n>&`:Duplicate standard output to file descriptor n.
18 | - `n<&`:Duplicate standard input from file descriptor n.
19 | - `n>&m`:File descriptor n is made to be a copy of the output file descriptor.
20 | - `n<&m`:File descriptor n is made to be a copy of the input file descriptor.
21 | - `&>file`:Directs standard output and standard error to file.
22 | - `<&-`:Close the standard input.
23 | - `>&-`:Close the standard output.
24 | - `n>&-`:Close the output from file descriptor n.
25 | - `n<&-`:Close the input from file descriptor n.
26 | - `n>&word`:If n is not specified, the standard output (file descriptor 1) is used. If the digits in word do not specify a file descriptor open for output, a redirection error occurs. As a special case, if n is omitted, and word does not expand to one or more digits, the standard output and standard error are redirected as described previously.
27 | - `n<&word`:If word expands to one or more digits, the file descriptor denoted by n is made to be a copy of that file descriptor. If the digits in word do not specify a file descriptor open for input, a redirection error occurs. If word evaluates to -, file descriptor n is closed. If n is not specified, the standard input (file descriptor 0) is used.
28 | - `n>&digit-`:Moves the file descriptor digit to file descriptor n, or the standard output (file descriptor 1) if n is not specified.
29 | - `n<&digit-`:Moves the file descriptor digit to file descriptor n, or the standard input (file descriptor 0) if n is not specified. digit is closed after being duplicated to n.
30 |
31 |
32 | `>`用来将标准输出重定向到指定文件。
33 |
34 | ```bash
35 | $ ls -l /usr/bin > ls-output.txt
36 | ```
37 |
38 | 如果重定向后的指定文件已经存在,就会被覆盖,不会有任何提示。
39 |
40 | 如果命令没有任何输出,那么重定向之后,得到的是一个长度为`0`的文件。因此,`>`具有创建新文件或改写现存文件、将其改为长度`0`的作用。
41 |
42 | ```bash
43 | $ > ls-output.txt
44 | ```
45 |
46 | `>>`用来将标准输出重定向追加到指定文件。
47 |
48 | ```bash
49 | $ ls -l /usr/bin >> ls-output.txt
50 | ```
51 |
52 | `2>`用来将标准错误重定向到指定文件。
53 |
54 | ```bash
55 | $ ls -l /bin/usr 2> ls-error.txt
56 | ```
57 |
58 | 标准输出和标准错误,可以重定向到同一个文件。
59 |
60 | ```bash
61 | $ ls -l /bin/usr > ls-output.txt 2>&1
62 | # 或者
63 | $ ls -l /bin/usr &> ls-output.txt
64 |
65 | # 追加到同一个文件
66 | $ ls -l /bin/usr &>> ls-output.txt
67 | ```
68 |
69 | 如果不希望输出错误信息,可以将它重定向到一个特殊文件`/dev/null`。
70 |
71 | ```bash
72 | $ ls -l /bin/usr 2> /dev/null
73 | ```
74 |
75 | `|`用于将一个命令的标准输出,重定向到另一个命令的标准输入。
76 |
77 | ```bash
78 | $ ls -l /usr/bin | less
79 | ```
80 |
81 | 不要将`>`与`|`混淆。
82 |
83 | ```bash
84 | $ ls > less
85 | ```
86 |
87 | 上面命令会在当前目录,生成一个名为`less`的文本文件。
88 |
89 | 下面是标准错误重定向的一个例子。
90 |
91 | ```bash
92 | invalid_input () {
93 | echo "Invalid input '$REPLY'" >&2
94 | exit 1
95 | }
96 | read -p "Enter a single item > "
97 | [[ -z $REPLY ]] && invalid_input
98 | ```
99 |
100 | ## tee
101 |
102 | `tee`命令用于同时将标准输出重定向到文件,以及另一个命令的标准输入。
103 |
104 | ```bash
105 | $ ls /usr/bin | tee ls.txt | grep zip
106 | ```
107 |
108 | ## 命令替换
109 |
110 | 命令替换(command substitution)指的是将一个命令的输出,替换进入另一个命令。`$(command)`表示命令替换,另一种写法是使用反引号。
111 |
112 | ```bash
113 | $ echo $(ls)
114 | # 或者
115 | $ echo `ls`
116 |
117 | $ ls -l $(which cp)
118 | # 或者
119 | $ ls -l `which cp`
120 | ```
121 |
122 | ## basename
123 |
124 | `basename`命令清除 一个路径名的开头部分,只留下一个文件的基本名称。
125 |
126 | ```bash
127 | #!/bin/bash
128 | # file_info: simple file information program
129 | PROGNAME=$(basename $0)
130 | if [[ -e $1 ]]; then
131 | echo -e "\nFile Type:"
132 | file $1
133 | echo -e "\nFile Status:"
134 | stat $1
135 | else
136 | echo "$PROGNAME: usage: $PROGNAME file" >&2
137 | exit 1
138 | fi
139 | ```
140 |
--------------------------------------------------------------------------------
/docs/archives/regex.md:
--------------------------------------------------------------------------------
1 | # 正则表达式
2 |
3 | `正则表达式`是表达文本模式的方法。
4 |
5 | - `.`:匹配任何单个字符。
6 | - `?`:上一项是可选的,最多匹配一次。
7 | - `*`:前一项将被匹配零次或多次。
8 | - `+`:前一项将被匹配一次或多次。
9 | - `{N}`:上一项完全匹配N次。
10 | - `{N,}`:前一项匹配N次或多次。
11 | - `{N,M}`:前一项至少匹配N次,但不超过M次。
12 | - `--`:表示范围,如果它不是列表中的第一个或最后一个,也不是列表中某个范围的终点。
13 | - `^`:匹配行首的空字符串;也代表不在列表范围内的字符。
14 | - `$`:匹配行尾的空字符串。
15 | - `\b`:匹配单词边缘的空字符串。
16 | - `\B`:匹配空字符串,前提是它不在单词的边缘。
17 | - `\<`:匹配单词开头的空字符串。
18 | - `\>`:匹配单词末尾的空字符串。
19 |
20 | ## 元字符
21 |
22 | `元字符`是表示特殊函数的字符,包括以下这些`^ $ . [ ] { } - ? * + ( ) | \\`。除了元字符,其他字符在正则表达式中,都表示原来的含义。
23 |
24 | - `.` 匹配任意字符,但不含空字符
25 | - `^` 匹配文本行开头
26 | - `$` 匹配文本行结尾
27 |
28 | ```bash
29 | $ grep -h '.zip' dirlist*.txt
30 | ```
31 |
32 | 上面命令在文件中查找包含正则表达式“.zip”的文本行。注意,上面命令不会匹配`zip`程序,因为`zip`只有三个字符,而`.zip`要求四个字符。
33 |
34 | ```bash
35 | $ grep -h '^zip' dirlist*.txt
36 | $ grep -h 'zip$' dirlist*.txt
37 | ```
38 |
39 | 上面命令分别在文件列表中搜索行首,行尾以及行首和行尾同时包含字符串“zip”(例如,zip 独占一行)的匹配行。 注意正则表达式‘^$’(行首和行尾之间没有字符)会匹配空行。
40 |
41 | ## 方括号
42 |
43 | 方括号之中的字符,表示可以任意匹配其中的一个。
44 |
45 | ```bash
46 | $ grep -h '[bg]zip' dirlist*.txt
47 | ```
48 |
49 | 上面命令匹配包含字符串“bzip”或者“gzip”的任意行。
50 |
51 | 注意,元字符放入方括号之中,会失去其特殊含义。但有两种情况除外,`^`在方括号的开头,表示否定,否则只是一个普通字符,表示原义。
52 |
53 | ```bash
54 | $ grep -h '[^bg]zip' dirlist*.txt
55 | ```
56 |
57 | 上面命令匹配不以`b`或`g`开头的`zip`字符串。注意,上面命令不会匹配`zip`,因为一个否定的字符集仍然要求存在一个字符。
58 |
59 | `-`在方括号之中表示一个字符区域。
60 |
61 | ```bash
62 | $ grep -h '^[A-Z]' dirlist*.txt
63 | ```
64 |
65 | 上面命令匹配所有以大写字母开头的文本行。类似的,`^[A-Za-z0-9]`表示以大写字母、小写字母、数字开头的文本行。
66 |
67 | 注意,连字号如果不构成一个字符区域,则表示其本来的含义。
68 |
69 | ```bash
70 | $ grep -h '[-AZ]' dirlist*.txt
71 | ```
72 |
73 | 上面命令匹配包含一个连字符,或一个大写字母“A”,或一个大写字母“Z”的文件名。
74 |
75 | ## 预定义字符类
76 |
77 | 由于`locale`设置不同,Shell展开正则表达式`[A-Z]`时,可能不是解释为所有大写字母,而是解释为包括所有字母的字典顺序。
78 |
79 | ```bash
80 | $ ls /usr/sbin/[A-Z]*
81 | ```
82 |
83 | 上面命令在某些发行版里面,会返回所有大写字母或小写字母开头的文件。
84 |
85 | 为了避免这个问题,可以使用正则表达式的预定义字符类。
86 |
87 | - `[:alnum:]` 字母数字字符。在 ASCII 中,等价于:`[A-Za-z0-9]`
88 | - `[:word:]` 与`[:alnum:]`相同, 但增加了下划线字符。
89 | - `[:alpha:]` 字母字符。在 ASCII 中,等价于`[A-Za-z]`
90 | - `[:blank:]` 包含空格和 tab 字符。
91 | - `[:cntrl:]` ASCII 的控制码。包含了0到31,和127的 ASCII 字符。
92 | - `[:digit:]` 数字0到9
93 | - `[:graph:]` 可视字符。在 ASCII 中,它包含33到126的字符。
94 | - `[:lower:]` 小写字母。
95 | - `[:punct:]` 标点符号字符。
96 | - `[:print:]` 可打印的字符。等于`[:graph:]`中的所有字符,再加上空格字符。
97 | - `[:space:]` 空白字符,包括空格,tab,回车,换行,vertical tab, 和 form feed.在 ASCII 中, 等价于`[ \t\r\n\v\f]`
98 | - `[:upper:]` 大写字母。
99 | - `[:xdigit:]` 用来表示十六进制数字的字符。在 ASCII 中,等价于`[0-9A-Fa-f]`
100 |
101 | ```bash
102 | $ ls /usr/sbin/[[:upper:]]*
103 | ```
104 |
105 | 上面命令返回所有大写字母开头的文件名。
106 |
107 | ## 选择
108 |
109 | `|`表示匹配一系列字符串之中的一个。注意与方括号区分,方括号表示匹配一系列字符之中的一个。
110 |
111 | ```bash
112 | $ echo "AAA" | grep -E 'AAA|BBB'
113 | AAA
114 | $ echo "BBB" | grep -E 'AAA|BBB'
115 | BBB
116 | $ echo "CCC" | grep -E 'AAA|BBB'
117 | $
118 | ```
119 |
120 | 上面代码中,`AAA|BBB`表示匹配字符串`AAA`或者是字符串`BBB`。`grep`程序使用`-E`参数,表示按照正则表达式规则匹配。并且,这个正则表达式放在单引号之中,为的是阻止Shell把`|`解释为管道操作符。
121 |
122 | `|`可以多个连用,也可以与其他正则规则结合使用。
123 |
124 | ```bash
125 | $ echo "AAA" | grep -E 'AAA|BBB|CCC'
126 |
127 | $ grep -Eh '^(bz|gz|zip)' dirlist*.txt
128 | ```
129 |
130 | ## 量词操作符
131 |
132 | 量词操作符表示一个元素被匹配的次数。
133 |
134 | - `?` 匹配前面的元素出现0次或1次
135 | - `*` 匹配前面的元素出现0次或多次
136 | - `+` 匹配前面的元素出现1次或多次
137 | - `{n}` 匹配前面的元素出现了`n`次
138 | - `{n,m}` 匹配前面的元素它至少出现了`n`次,但是不多于`m`次
139 | - `{n,}` 匹配前面的元素至少出现了`n`次
140 | - `{,m}` 匹配前面的元素,如果它出现的次数不多于 m 次。
141 |
--------------------------------------------------------------------------------
/docs/archives/system.md:
--------------------------------------------------------------------------------
1 | # 系统信息
2 |
3 | ## uname
4 |
5 | `uname`命令返回当前机器的信息。
6 |
7 | ```bash
8 | # 内核的版本
9 | $ uname -r
10 | 3.2.0-24-virtual
11 |
12 | # CPU 架构
13 | $ uname -m
14 | x86_64
15 | ```
16 |
17 | 如果要了解操作系统的版本,可以查看`/etc/issue`文件。
18 |
19 | ```bash
20 | $ cat /etc/issue
21 | Debian GNU/Linux 9 \n \l
22 | ```
23 |
24 | ## service
25 |
26 | `service`命令可以查看当前正在运行的服务。
27 |
28 | ```bash
29 | $ service --status-all
30 | [ + ] apache2
31 | [ ? ] atd
32 | [ - ] bootlogd
33 | ```
34 |
35 | 上面代码中,`+`表示正在运行,`-`表示已经停止,`?`表示`service`命令不了解相关信息。
36 |
--------------------------------------------------------------------------------
/docs/archives/text.md:
--------------------------------------------------------------------------------
1 | # 文本处理
2 |
3 | ## cat
4 |
5 | `cat`可以文件的内容,显示在标准输出。
6 |
7 | ```bash
8 | $ cat text1
9 | 1 apple
10 | 2 pear
11 | 3 banana
12 | ```
13 |
14 | 它也可以同时输出多个文件内容。
15 |
16 | ```bash
17 | $ cat text1 text2
18 | ```
19 |
20 | 它与重定向结合,就可以合并多个文件。
21 |
22 | ```bash
23 | # 合并文本文件
24 | $ cat text* > text.all
25 |
26 | # 合并二进制文件
27 | $ cat movie.mpeg.0* > movie.mpeg
28 | ```
29 |
30 | 如果调用`cat`命令时没有任何参数,它将读取标准输入,然后显示到标准输出。按下`Ctrl + d`,将会结束`cat`读取标准输入。利用这一点,可以将键盘输入写入指定文件,按下`Ctrl + d`结束输入。
31 |
32 | ```bash
33 | $ cat > lazy_dog.txt
34 | ```
35 |
36 | 它的参数如下。
37 |
38 | - `-n` 输出结果显示行号
39 | - `-s` 将多个连续的空白行,输出为一行
40 | - `-A` 输出结果中显示控制符,比如Tab键显示为`^I`,行尾显示`$`
41 |
42 | `cat`支持Here document,显示多行文本。
43 |
44 | ```bash
45 | cat << _EOF_
46 |
47 |
48 | $TITLE
49 |
50 |
51 | $TITLE
52 | $TIME_STAMP
53 |
54 |
55 | _EOF_
56 | ```
57 |
58 | Here document 常在脚本当中作为输入的手段。
59 |
60 | ```bash
61 | $ sort -k2 < 1 apple
63 | > 2 pear
64 | > 3 banana
65 | > END
66 | 1 apple
67 | 3 banana
68 | 2 pear
69 | ```
70 |
71 | 如果使用`<<-`代替`<<`,行首的tab键将被剥离。
72 |
73 | ## nl
74 |
75 | `nl`命令为文本文件添加行号,显示在标准输出。
76 |
77 | ```bash
78 | $ nl example.txt
79 | ```
80 |
81 | ## sort
82 |
83 | `sort`命令将文本文件的所有行排序后输出。
84 |
85 | ```bash
86 | $ sort file1.txt file2.txt file3.txt > final_sorted_list.txt
87 | ```
88 |
89 | 它的参数如下。
90 |
91 | - `-b` `--ignore-leading-blanks` 默认情况下,排序用的是每行的第一个字符。这个参数忽略每行开头的空格,从第一个非空白字符开始排序。
92 | - `-f` `--ignore-case` 让排序不区分大小写。
93 | - `-n` `--numeric-sort` 按照数值排序,而不是字符值,用于行首是数值的情况。
94 | - `-r` `--reverse` 按相反顺序排序。结果按照降序排列,而不是升序。
95 | - `-k` `--key=field1[,field2]` 指定按照每行的第几个字段(从1开始)排序,而不是按照行首字符排序。该属性可以多个连用,用于指定多重排序标准,还可以指定每个字段指定排序标准,这些值与全局属性一致,比如b(忽略开头的空格),n(数值排序),r(逆向排序)等等。
96 | - `-m` `--merge` 把每个参数看作是一个预先排好序的文件。把多个文件合并成一个排好序的文件,而没有执行额外的排序。
97 | - `-o` `--output=file` 把排好序的输出结果发送到文件,而不是标准输出。
98 | - `-t` `--field-separator=char` 定义字段分隔字符。默认情况下,字段由空格或制表符分隔。
99 | - `-u` 输出结果中删除重复行
100 |
101 | ```bash
102 | $ sort --key=1,1 --key=2n distros.txt
103 | ```
104 |
105 | 上面命令中,第一个`--key`指定第一排序标准是只用第一字段(`1,1`),也可以指定使用第一字段第一个字符(`1.1`);第二排序标准是第二字段,按数值排序。
106 |
107 | ## uniq
108 |
109 | `uniq`命令在排序后的行中,删除所有重复的行,保证所有输出没有重复。
110 |
111 | ```bash
112 | $ ls /bin /usr/bin | sort | uniq
113 | ```
114 |
115 | 它的参数如下。
116 |
117 | - `-c` 输出所有的重复行,并且每行开头显示重复的次数。
118 | - `-d` 只输出重复行,而不是不重复的文本行。
119 | - `-f n` 忽略每行开头的 n 个字段,字段之间由空格分隔,正如 sort 程序中的空格分隔符;然而, 不同于 sort 程序,uniq 没有选项来设置备用的字段分隔符。
120 | - `-i` 在比较文本行的时候忽略大小写。
121 | - `-s n` 跳过(忽略)每行开头的 n 个字符。
122 | - `-u` 只是输出独有的文本行。这是默认的。
123 | - `-V` 按照版本号排序。
124 |
125 | `-V`参数可以按版本号排列(从小到大)。
126 |
127 | ```bash
128 | $ sort -V input.txt
129 | 1.0.15
130 | 1.3.0
131 | 2.1.2
132 | 3.0.0
133 | ```
134 |
135 | `-rV`参数可以按版本号逆序排列。
136 |
137 | ```bash
138 | $ sort -rV input.txt
139 | 3.0.0
140 | 2.1.2
141 | 1.3.0
142 | 1.0.15
143 | ```
144 |
145 | ## cut
146 |
147 | `cut`程序用来从文本行中抽取文本,并把其输出到标准输出。它能够接受多个文件参数或者标准输入。
148 |
149 | 它的参数如下。
150 |
151 | - `-c char_list` 抽取指定范围的文本
152 | - `-f field_list` 抽取指定字段,字段之间可以tab分隔也可以逗号分隔
153 | - `-d delim_char` 指定字段分隔符,默认是tab键
154 | - `--complement` 抽取整个文本行,除了那些由-c 和/或-f 选项指定的文本。
155 |
156 | ```bash
157 | # 抽取每行的第三个字段
158 | $ cut -f 3 distros.txt
159 |
160 | # 抽取每行的第7到第10个字符
161 | $ cut -c 7-10 distros.txt
162 |
163 | # 抽取每行的第23个到结尾的字符1
164 | $ cut -c 23- distros.txt
165 |
166 | # 指定字段分隔符为冒号
167 | $ cut -d ':' -f 1 /etc/passwd
168 | ```
169 |
170 | ## paste
171 |
172 | `paste`程序将多个文本文件按行合并,即每一行都由原来文本文件的每一行组成,显示在标准输出。
173 |
174 | ```bash
175 | $ paste distros-dates.txt distros-versions.txt
176 | ```
177 |
178 | ## wc
179 |
180 | `wc`命令输出一个文本文件的统计信息(word count),一共有三个值,分别为行数、词数和字节数。
181 |
182 | ```bash
183 | $ wc ls-output.txt
184 | 7902 64566 503634 ls-output.txt
185 | ```
186 |
187 | 如果使用`-l`参数,则只输出行数。
188 |
189 | ```bash
190 | $ ls /bin /usr/bin | sort | uniq | wc -l
191 | 2728
192 | ```
193 |
194 | ## head
195 |
196 | `head`命令返回文本文件的头部,默认显示10行。
197 |
198 | `-n`参数指定显示的行数。
199 |
200 | ```bash
201 | $ head -n 5 ls-output.txt
202 | ```
203 |
204 | ## tail
205 |
206 | `tail`命令返回文本文件的尾部,默认显示10行。
207 |
208 | `-n`参数指定显示的行数。
209 |
210 | ```bash
211 | $ tail -n 5 ls-output.txt
212 | ```
213 |
214 | `-f`会实时追加显示新增的内容,常用于实时监控日志,按`Ctrl + c`停止。
215 |
216 | ```bash
217 | $ tail -f /var/log/messages
218 | ```
219 |
220 | ## grep
221 |
222 | `grep`程序用于在指定文件之中,搜索符合某个模式的行,并把搜索结果输出到标准输出。
223 |
224 | ```bash
225 | $ grep keyword foo.txt
226 | ```
227 |
228 | 上面命令输出`foo.txt`之中匹配`keyword`的行。
229 |
230 | `grep`程序可以同时搜索多个文件。
231 |
232 | ```bash
233 | $ grep keyword f*.txt
234 | ```
235 |
236 | 上面命令输出多个文件中匹配`keyword`的行。
237 |
238 | `-l`参数输出匹配的文件名,而不是文件行。
239 |
240 | ```bash
241 | $ grep -l bzip dirlist*.txt
242 | ```
243 |
244 | 如果想搜索文件名,而不是文件内容,可以使用重定向。
245 |
246 | ```bash
247 | $ ls /usr/bin | grep zip
248 | ```
249 |
250 | 上面命令会输出`/usr/bin`目录中,文件名中包含子字符串`zip`的所有文件。
251 |
252 | 参数的含义。
253 |
254 | - `-c`或`--count` 输出匹配的数量,而不是匹配的文本行。如果使用了`-v`,则输出不匹配的数量。
255 | - `-h`或`--no-filename` 应用于多文件搜索,不在每行匹配的文本前,输出文件名
256 | - `-i`或`--ignore-case` 忽略大小写
257 | - `-l`或`--files-with-matches` 输出包含匹配项的文件名,而不是文本行本身
258 | - `-L`或`--files-without-match` 类似于`-l`,但输出不包含匹配项的文件名
259 | - `-n`或`--line-number` 每个匹配行之前输出其对应的行号
260 | - `-v`或`--invert-match` 只返回不符合模式的行
261 |
262 | ## sed
263 |
264 | `sed`是一个强大的文本编辑工具。
265 |
266 | ```bash
267 | # 输出前5行
268 | $ sed -n '1,5p' distros.txt
269 |
270 | # 输出包含指定内容的行
271 | $ sed -n '/SUSE/p' distros.txt
272 |
273 | # 输出不包含指定内容的行
274 | $ sed -n '/SUSE/!p' distros.txt
275 |
276 | # 替换内容(只替换第一个)
277 | $ sed 's/regexp/replacement/' distros.txt
278 |
279 | # 替换内容(全局替换)
280 | $ sed 's/regexp/replacement/g' distros.txt
281 | ```
282 |
--------------------------------------------------------------------------------
/docs/archives/time.md:
--------------------------------------------------------------------------------
1 | # 时间管理
2 |
3 | ## date 命令
4 |
5 | `date`命令用于输出当前时间
6 |
7 | ```bash
8 | $ date
9 | 2016年 03月 14日 星期一 17:32:35 CST
10 | ```
11 |
12 | `date`命令后面用加号(`+`)指定显示的格式。
13 |
14 | ```bash
15 | $ date +%d_%b_%Y
16 | 10_Sep_2018
17 |
18 | $ date +%D
19 | 09/10/18
20 |
21 | $ date +%F-%T
22 | 2018-09-10-11:09:51
23 | ```
24 |
25 | 完整的格式参数如下。
26 |
27 | - %a 星期名的缩写(Sun)
28 | - %A 星期名的全称(Sunday)
29 | - %b 月份的缩写(Jan)
30 | - %B 月份的全称(January)
31 | - %c 日期和时间(Thu Mar 3 23:05:25 2005)
32 | - %C 世纪,就是年份数省略后两位(20)
33 | - %d 一个月的第几天(01)
34 | - %D 日期,等同于`%m/%d/%y`
35 | - %e 一个月的第几天,用空格补零,等同于`%_d`
36 | - %F 完整的日期,等同于`%Y-%m-%d`
37 | - %g last two digits of year of ISO week number (see %G)
38 | - %G year of ISO week number (see %V); normally useful only with %V
39 | - %h 等同于`%b`
40 | - %H 小时(00..23)
41 | - %I 小时(01..12)
42 | - %j day of year (001..366)
43 | - %k hour ( 0..23)
44 | - %l hour ( 1..12)
45 | - %m month (01..12)
46 | - %M minute (00..59)
47 | - %N nanoseconds (000000000..999999999)
48 | - %p locale’s equivalent of either AM or PM; blank if not known
49 | - %P like %p, but lower case
50 | - %r locale’s 12-hour clock time (e.g., 11:11:04 PM)
51 | - %R 24-hour hour and minute; same as %H:%M
52 | - %s seconds since 1970-01-01 00:00:00 UTC
53 | - %S second (00..60)
54 | - %T time; same as %H:%M:%S
55 | - %u day of week (1..7); 1 is Monday
56 | - %U week number of year, with Sunday as first day of week (00..53)
57 | - %V ISO week number, with Monday as first day of week (01..53)
58 | - %w day of week (0..6); 0 is Sunday
59 | - %W week number of year, with Monday as first day of week (00..53)
60 | - %x locale’s date representation (e.g., 12/31/99)
61 | - %X locale’s time representation (e.g., 23:13:48)
62 | - %y last two digits of year (00..99)
63 | - %Y year
64 | - %z +hhmm numeric timezone (e.g., -0400)
65 | - %:z +hh:mm numeric timezone (e.g., -04:00)
66 | - %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)
67 | - %Z alphabetic time zone abbreviation (e.g., EDT)
68 |
69 | ## cal 命令
70 |
71 | `cal`命令用于显示日历。不带有参数时,显示的是当前月份。
72 |
73 | ```bash
74 | $ cal
75 | 三月 2016
76 | 日 一 二 三 四 五 六
77 | 1 2 3 4 5
78 | 6 7 8 9 10 11 12
79 | 13 14 15 16 17 18 19
80 | 20 21 22 23 24 25 26
81 | 27 28 29 30 31
82 | ```
83 |
--------------------------------------------------------------------------------
/docs/archives/user.md:
--------------------------------------------------------------------------------
1 | # 用户管理
2 |
3 | ## id
4 |
5 | `id`命令用于查看指定用户的用户名和组名。
6 |
7 | ```bash
8 | $ id
9 | uid=500(me) gid=500(me) groups=500(me)
10 | ```
11 |
12 | `id`输出结果分为三个部分,分别是UID(用户编号和用户名)、GID(组编号和组名),groups(用户所在的所有组)。
13 |
14 | 用户帐户的信息,存放在`/etc/passwd`文件里面;用户组的信息,存放在`/etc/group`文件里面。
15 |
16 | ```bash
17 | # 返回UID
18 | $ id -u [UserName]
19 |
20 | # 返回GID
21 | $ id -g [UserName]
22 |
23 | # 返回用户名
24 | $ id -un [UserName]
25 |
26 | # 返回组名
27 | $ id -gn [UserName]
28 | ```
29 |
30 | 上面的命令,如果省略用户名,则返回当前用户的信息。
31 |
32 | ## su
33 |
34 | `su`命令允许你以另一个用户的身份,启动一个新的 shell 会话,或者是以这个用户的身份来发布一个命令。
35 |
36 | ```bash
37 | $ su otherUser
38 | ```
39 |
40 | 执行上面的命令以后,系统会提示输入密码。通过以后,就以另一个用户身份在执行命令了。
41 |
42 | 如果不加用户名,则表示切换到root用户。
43 |
44 | ```bash
45 | $ su
46 | ```
47 |
48 | `-l`参数表示启动一个需要登录的新的Shell,这意味着工作目录会切换到该用户的主目录。它的缩写形式是`-`。
49 |
50 | ```bash
51 | $ su -
52 | ```
53 |
54 | 上面命令表示,切换到root用户的身份,且工作目录也切换到root用户的主目录。
55 |
56 | `-c`参数表示只以其他用户的身份,执行单个命令,而不是启动一个新的Session。
57 |
58 | ```bash
59 | $ su -c 'command'
60 |
61 | # 实例
62 | $ su -c 'ls -l /root/*'
63 | ```
64 |
65 | ## sudo
66 |
67 | `sudo`命令很类似`su`命令,但有几点差别。
68 |
69 | - 对于管理员来说,`sudo`命令的可配置性更高
70 | - `sudo`命令通常只用于执行单个命令,而不是开启另一个Session。
71 | - `sudo`命令不要求超级用户的密码,而是用户使自己的密码来认证。
72 |
73 | `sudo`的设置在文件`/etc/sudoers`之中。
74 |
75 | `-l`参数列出用户拥有的所有权限。
76 |
77 | ```bash
78 | $ sudo -l
79 | ```
80 |
81 | ## chown
82 |
83 | `chown`命令用来更改文件或目录的所有者和用户组。使用这个命令需要超级用户权限。
84 |
85 | ```bash
86 | $ chown [owner][:[group]] file
87 | ```
88 |
89 | 下面是一些例子。
90 |
91 | ```bash
92 | # 更改文件所有者
93 | $ sudo chown bob foo.txt
94 |
95 | # 更改文件所有者和用户组
96 | $ sudo chown bob:users foo.txt
97 |
98 | # 更改用户组
99 | $ sudo chown :admins foo.txt
100 |
101 | # 更改文件所有者和用户组(用户 bob 登录系统时,所属的用户组)
102 | $ sudo chown bob: foo.txt
103 | ```
104 |
105 | ## chgrp
106 |
107 | `chgrp`命令更改用户组,用法与`chown`命令类似。
108 |
109 | ## useradd
110 |
111 | `useradd`命令用来新增用户。
112 |
113 | ```bash
114 | $ useradd -G admin -d /home/bill -s /bin/bash -m bill
115 | ```
116 |
117 | 上面命令新增用户`bill`,参数`-G`指定用户所在的组,参数`d`指定用户的主目录,参数`s`指定用户的 Shell,参数`m`表示如果该目录不存在,则创建该目录。
118 |
119 | ## usermod
120 |
121 | `usermod`命令用来修改用户的各项属性。
122 |
123 | ```bash
124 | $ usermod -g sales jerry
125 | ```
126 |
127 | 上面的命令修改用户`jerry`属于的主要用户组为`sales`。
128 |
129 | ```bash
130 | $ usermod -G sales jerry
131 | ```
132 |
133 | 上面的命令修改用户`jerry`属于的次要用户组为`sales`。
134 |
135 | ## adduser
136 |
137 | `adduser`命令用来将一个用户加入用户组。
138 |
139 | ```bash
140 | $ sudo adduser username grouptoadd
141 | ```
142 |
143 | ## groupadd
144 |
145 | `groupadd`命令用来新建一个用户组。
146 |
147 | ```bash
148 | $ sudo groupadd group1
149 | $ sudo adduser foobar group1
150 | ```
151 |
152 | ## groupdel
153 |
154 | `groupdel`命令用来删除一个用户组。
155 |
156 | ```bash
157 | $ sudo groupdel group1
158 | ```
159 |
160 | ## passwd
161 |
162 | `passwd`命令用于修改密码。
163 |
164 | ```bash
165 | # 修改自己的密码
166 | $ passwd
167 |
168 | # 修改其他用户的密码
169 | $ sudo passwd [user]
170 | ```
171 |
--------------------------------------------------------------------------------
/docs/arithmetic.md:
--------------------------------------------------------------------------------
1 | # Bash 的算术运算
2 |
3 | ## 算术表达式
4 |
5 | `((...))`语法可以进行整数的算术运算。
6 |
7 | ```bash
8 | $ ((foo = 5 + 5))
9 | $ echo $foo
10 | 10
11 | ```
12 |
13 | `((...))`会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。
14 |
15 | ```bash
16 | $ ((2+2))
17 | $ (( 2+2 ))
18 | $ (( 2 + 2 ))
19 | ```
20 |
21 | 这个语法不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是`0`,命令就算执行成功。
22 |
23 | ```bash
24 | $ (( 3 + 2 ))
25 | $ echo $?
26 | 0
27 | ```
28 |
29 | 上面例子中,`3 + 2`的结果是5,命令就算执行成功,环境变量`$?`为`0`。
30 |
31 | 如果算术结果为`0`,命令就算执行失败。
32 |
33 | ```bash
34 | $ (( 3 - 3 ))
35 | $ echo $?
36 | 1
37 | ```
38 |
39 | 上面例子中,`3 - 3`的结果是`0`,环境变量`$?`为`1`,表示命令执行失败。
40 |
41 | 如果要读取算术运算的结果,需要在`((...))`前面加上美元符号`$((...))`,使其变成算术表达式,返回算术运算的值。
42 |
43 | ```bash
44 | $ echo $((2 + 2))
45 | 4
46 | ```
47 |
48 | `((...))`语法支持的算术运算符如下。
49 |
50 | - `+`:加法
51 | - `-`:减法
52 | - `*`:乘法
53 | - `/`:除法(整除)
54 | - `%`:余数
55 | - `**`:指数
56 | - `++`:自增运算(前缀或后缀)
57 | - `--`:自减运算(前缀或后缀)
58 |
59 | 注意,除法运算符的返回结果总是整数,比如`5`除以`2`,得到的结果是`2`,而不是`2.5`。
60 |
61 | ```bash
62 | $ echo $((5 / 2))
63 | 2
64 | ```
65 |
66 | `++`和`--`这两个运算符有前缀和后缀的区别。作为前缀是先运算后返回值,作为后缀是先返回值后运算。
67 |
68 | ```bash
69 | $ i=0
70 | $ echo $i
71 | 0
72 | $ echo $((i++))
73 | 0
74 | $ echo $i
75 | 1
76 | $ echo $((++i))
77 | 2
78 | $ echo $i
79 | 2
80 | ```
81 |
82 | 上面例子中,`++`作为后缀是先返回值,执行`echo`命令,再进行自增运算;作为前缀则是先进行自增运算,再返回值执行`echo`命令。
83 |
84 |
85 | `$((...))`内部可以用圆括号改变运算顺序。
86 |
87 | ```bash
88 | $ echo $(( (2 + 3) * 4 ))
89 | 20
90 | ```
91 |
92 | 上面例子中,内部的圆括号让加法先于乘法执行。
93 |
94 | `$((...))`结构可以嵌套。
95 |
96 | ```bash
97 | $ echo $(((5**2) * 3))
98 | 75
99 | # 等同于
100 | $ echo $(($((5**2)) * 3))
101 | 75
102 | ```
103 |
104 | 这个语法只能计算整数,否则会报错。
105 |
106 | ```bash
107 | # 报错
108 | $ echo $((1.5 + 1))
109 | bash: 语法错误
110 | ```
111 |
112 | `$((...))`的圆括号之中,不需要在变量名之前加上`$`,不过加上也不报错。
113 |
114 | ```bash
115 | $ number=2
116 | $ echo $(($number + 1))
117 | 3
118 | ```
119 |
120 | 上面例子中,变量`number`前面有没有美元符号,结果都是一样的。
121 |
122 | 如果在`$((...))`里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错。
123 |
124 | ```bash
125 | $ echo $(( "hello" + 2))
126 | 2
127 | $ echo $(( "hello" * 2))
128 | 0
129 | ```
130 |
131 | 上面例子中,`"hello"`会被当作变量名,返回空值,而`$((...))`会将空值当作`0`,所以乘法的运算结果就是`0`。同理,如果`$((...))`里面使用不存在的变量,也会当作`0`处理。
132 |
133 | 如果一个变量的值为字符串,跟上面的处理逻辑是一样的。即该字符串如果不对应已存在的变量,在`$((...))`里面会被当作空值。
134 |
135 | ```bash
136 | $ foo=hello
137 | $ echo $(( foo + 2))
138 | 2
139 | ```
140 |
141 | 上面例子中,变量`foo`的值是`hello`,而`hello`也会被看作变量名。这使得有可能写出动态替换的代码。
142 |
143 | ```bash
144 | $ foo=hello
145 | $ hello=3
146 | $ echo $(( foo + 2 ))
147 | 5
148 | ```
149 |
150 | 上面代码中,`foo + 2`取决于变量`hello`的值。
151 |
152 | 最后,`$[...]`是以前的语法,也可以做整数运算,不建议使用。
153 |
154 | ```bash
155 | $ echo $[2+2]
156 | 4
157 | ```
158 |
159 | ## 数值的进制
160 |
161 | Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。
162 |
163 | - `number`:没有任何特殊表示法的数字是十进制数(以10为底)。
164 | - `0number`:八进制数。
165 | - `0xnumber`:十六进制数。
166 | - `base#number`:`base`进制的数。
167 |
168 | 下面是一些例子。
169 |
170 | ```bash
171 | $ echo $((0xff))
172 | 255
173 | $ echo $((2#11111111))
174 | 255
175 | ```
176 |
177 | 上面例子中,`0xff`是十六进制数,`2#11111111`是二进制数。
178 |
179 | ## 位运算
180 |
181 | `$((...))`支持以下的二进制位运算符。
182 |
183 | - `<<`:位左移运算,把一个数字的所有位向左移动指定的位。
184 | - `>>`:位右移运算,把一个数字的所有位向右移动指定的位。
185 | - `&`:位的“与”运算,对两个数字的所有位执行一个`AND`操作。
186 | - `|`:位的“或”运算,对两个数字的所有位执行一个`OR`操作。
187 | - `~`:位的“否”运算,对一个数字的所有位取反。
188 | - `^`:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。
189 |
190 | 下面是右移运算符`>>`的例子。
191 |
192 | ```bash
193 | $ echo $((16>>2))
194 | 4
195 | ```
196 |
197 | 下面是左移运算符`<<`的例子。
198 |
199 | ```bash
200 | $ echo $((16<<2))
201 | 64
202 | ```
203 |
204 | 下面是`17`(二进制`10001`)和`3`(二进制`11`)的各种二进制运算的结果。
205 |
206 | ```bash
207 | $ echo $((17&3))
208 | 1
209 | $ echo $((17|3))
210 | 19
211 | $ echo $((17^3))
212 | 18
213 | ```
214 |
215 | ## 逻辑运算
216 |
217 | `$((...))`支持以下的逻辑运算符。
218 |
219 | - `<`:小于
220 | - `>`:大于
221 | - `<=`:小于或相等
222 | - `>=`:大于或相等
223 | - `==`:相等
224 | - `!=`:不相等
225 | - `&&`:逻辑与
226 | - `||`:逻辑或
227 | - `!`:逻辑否
228 | - `expr1?expr2:expr3`:三元条件运算符。若表达式`expr1`的计算结果为非零值(算术真),则执行表达式`expr2`,否则执行表达式`expr3`。
229 |
230 | 如果逻辑表达式为真,返回`1`,否则返回`0`。
231 |
232 | ```bash
233 | $ echo $((3 > 2))
234 | 1
235 | $ echo $(( (3 > 2) || (4 <= 1) ))
236 | 1
237 | ```
238 |
239 | 三元运算符执行一个单独的逻辑测试。它用起来类似于`if/then/else`语句。
240 |
241 | ```bash
242 | $ a=0
243 | $ echo $((a<1 ? 1 : 0))
244 | 1
245 | $ echo $((a>1 ? 1 : 0))
246 | 0
247 | ```
248 |
249 | 上面例子中,第一个表达式为真时,返回第二个表达式的值,否则返回第三个表达式的值。
250 |
251 | ## 赋值运算
252 |
253 | 算术表达式`$((...))`可以执行赋值运算。
254 |
255 | ```bash
256 | $ echo $((a=1))
257 | 1
258 | $ echo $a
259 | 1
260 | ```
261 |
262 | 上面例子中,`a=1`对变量`a`进行赋值。这个式子本身也是一个表达式,返回值就是等号右边的值。
263 |
264 | `$((...))`支持的赋值运算符,有以下这些。
265 |
266 | - `parameter = value`:简单赋值。
267 | - `parameter += value`:等价于`parameter = parameter + value`。
268 | - `parameter -= value`:等价于`parameter = parameter – value`。
269 | - `parameter *= value`:等价于`parameter = parameter * value`。
270 | - `parameter /= value`:等价于`parameter = parameter / value`。
271 | - `parameter %= value`:等价于`parameter = parameter % value`。
272 | - `parameter <<= value`:等价于`parameter = parameter << value`。
273 | - `parameter >>= value`:等价于`parameter = parameter >> value`。
274 | - `parameter &= value`:等价于`parameter = parameter & value`。
275 | - `parameter |= value`:等价于`parameter = parameter | value`。
276 | - `parameter ^= value`:等价于`parameter = parameter ^ value`。
277 |
278 | 下面是一个例子。
279 |
280 | ```bash
281 | $ foo=5
282 | $ echo $((foo*=2))
283 | 10
284 | ```
285 |
286 | 如果在表达式内部赋值,可以放在圆括号中,否则会报错。
287 |
288 | ```bash
289 | $ echo $(( a<1 ? (a+=1) : (a-=1) ))
290 | ```
291 |
292 | ## 求值运算
293 |
294 | 逗号`,`在`$((...))`内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。
295 |
296 | ```bash
297 | $ echo $((foo = 1 + 2, 3 * 4))
298 | 12
299 | $ echo $foo
300 | 3
301 | ```
302 |
303 | 上面例子中,逗号前后两个表达式都会执行,然后返回后一个表达式的值`12`。
304 |
305 | ## expr 命令
306 |
307 | `expr`命令支持算术运算,可以不使用`((...))`语法。
308 |
309 | ```bash
310 | $ expr 3 + 2
311 | 5
312 | ```
313 |
314 | `expr`命令支持变量替换。
315 |
316 | ```bash
317 | $ foo=3
318 | $ expr $foo + 2
319 | 5
320 | ```
321 |
322 | `expr`命令也不支持非整数参数。
323 |
324 | ```bash
325 | $ expr 3.5 + 2
326 | expr: 非整数参数
327 | ```
328 |
329 | 上面例子中,如果有非整数的运算,`expr`命令就报错了。
330 |
331 | ## let 命令
332 |
333 | `let`命令用于将算术运算的结果,赋予一个变量。
334 |
335 | ```bash
336 | $ let x=2+3
337 | $ echo $x
338 | 5
339 | ```
340 |
341 | 上面例子中,变量`x`等于`2+3`的运算结果。
342 |
343 | 注意,`x=2+3`这个式子里面不能有空格,否则会报错。`let`命令的详细用法参见《变量》一章。
344 |
345 |
--------------------------------------------------------------------------------
/docs/array.md:
--------------------------------------------------------------------------------
1 | # 数组
2 |
3 | 数组(array)是一个包含多个值的变量。成员的编号从0开始,数量没有上限,也没有要求成员被连续索引。
4 |
5 | ## 创建数组
6 |
7 | 数组可以采用逐个赋值的方法创建。
8 |
9 | ```bash
10 | ARRAY[INDEX]=value
11 | ```
12 |
13 | 上面语法中,`ARRAY`是数组的名字,可以是任意合法的变量名。`INDEX`是一个大于或等于零的整数,也可以是算术表达式。注意数组第一个元素的下标是0, 而不是1。
14 |
15 | 下面创建一个三个成员的数组。
16 |
17 | ```bash
18 | $ array[0]=val
19 | $ array[1]=val
20 | $ array[2]=val
21 | ```
22 |
23 | 数组也可以采用一次性赋值的方式创建。
24 |
25 | ```bash
26 | ARRAY=(value1 value2 ... valueN)
27 |
28 | # 等同于
29 |
30 | ARRAY=(
31 | value1
32 | value2
33 | value3
34 | )
35 | ```
36 |
37 | 采用上面方式创建数组时,可以按照默认顺序赋值,也可以在每个值前面指定位置。
38 |
39 | ```bash
40 | $ array=(a b c)
41 | $ array=([2]=c [0]=a [1]=b)
42 |
43 | $ days=(Sun Mon Tue Wed Thu Fri Sat)
44 | $ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
45 | ```
46 |
47 | 只为某些值指定位置,也是可以的。
48 |
49 | ```bash
50 | names=(hatter [5]=duchess alice)
51 | ```
52 |
53 | 上面例子中,`hatter`是数组的0号位置,`duchess`是5号位置,`alice`是6号位置。
54 |
55 | 没有赋值的数组元素的默认值是空字符串。
56 |
57 | 定义数组的时候,可以使用通配符。
58 |
59 | ```bash
60 | $ mp3s=( *.mp3 )
61 | ```
62 |
63 | 上面例子中,将当前目录的所有 MP3 文件,放进一个数组。
64 |
65 | 先用`declare -a`命令声明一个数组,也是可以的。
66 |
67 | ```bash
68 | $ declare -a ARRAYNAME
69 | ```
70 |
71 | `read -a`命令则是将用户的命令行输入,存入一个数组。
72 |
73 | ```bash
74 | $ read -a dice
75 | ```
76 |
77 | 上面命令将用户的命令行输入,存入数组`dice`。
78 |
79 | ## 读取数组
80 |
81 | ### 读取单个元素
82 |
83 | 读取数组指定位置的成员,要使用下面的语法。
84 |
85 | ```bash
86 | $ echo ${array[i]} # i 是索引
87 | ```
88 |
89 | 上面语法里面的大括号是必不可少的,否则 Bash 会把索引部分`[i]`按照原样输出。
90 |
91 | ```bash
92 | $ array[0]=a
93 |
94 | $ echo ${array[0]}
95 | a
96 |
97 | $ echo $array[0]
98 | a[0]
99 | ```
100 |
101 | 上面例子中,数组的第一个元素是`a`。如果不加大括号,Bash 会直接读取`$array`首成员的值,然后将`[0]`按照原样输出。
102 |
103 | ### 读取所有成员
104 |
105 | `@`和`*`是数组的特殊索引,表示返回数组的所有成员。
106 |
107 | ```bash
108 | $ foo=(a b c d e f)
109 | $ echo ${foo[@]}
110 | a b c d e f
111 | ```
112 |
113 | 这两个特殊索引配合`for`循环,就可以用来遍历数组。
114 |
115 | ```bash
116 | for i in "${names[@]}"; do
117 | echo $i
118 | done
119 | ```
120 |
121 | `@`和`*`放不放在双引号之中,是有差别的。
122 |
123 | ```bash
124 | $ activities=( swimming "water skiing" canoeing "white-water rafting" surfing )
125 | $ for act in ${activities[@]}; \
126 | do \
127 | echo "Activity: $act"; \
128 | done
129 |
130 | Activity: swimming
131 | Activity: water
132 | Activity: skiing
133 | Activity: canoeing
134 | Activity: white-water
135 | Activity: rafting
136 | Activity: surfing
137 | ```
138 |
139 | 上面的例子中,数组`activities`实际包含5个成员,但是`for...in`循环直接遍历`${activities[@]}`,导致返回7个结果。为了避免这种情况,一般把`${activities[@]}`放在双引号之中。
140 |
141 | ```bash
142 | $ for act in "${activities[@]}"; \
143 | do \
144 | echo "Activity: $act"; \
145 | done
146 |
147 | Activity: swimming
148 | Activity: water skiing
149 | Activity: canoeing
150 | Activity: white-water rafting
151 | Activity: surfing
152 | ```
153 |
154 | 上面例子中,`${activities[@]}`放在双引号之中,遍历就会返回正确的结果。
155 |
156 | `${activities[*]}`不放在双引号之中,跟`${activities[@]}`不放在双引号之中是一样的。
157 |
158 | ```bash
159 | $ for act in ${activities[*]}; \
160 | do \
161 | echo "Activity: $act"; \
162 | done
163 |
164 | Activity: swimming
165 | Activity: water
166 | Activity: skiing
167 | Activity: canoeing
168 | Activity: white-water
169 | Activity: rafting
170 | Activity: surfing
171 | ```
172 |
173 | `${activities[*]}`放在双引号之中,所有成员就会变成单个字符串返回。
174 |
175 | ```bash
176 | $ for act in "${activities[*]}"; \
177 | do \
178 | echo "Activity: $act"; \
179 | done
180 |
181 | Activity: swimming water skiing canoeing white-water rafting surfing
182 | ```
183 |
184 | 所以,拷贝一个数组的最方便方法,就是写成下面这样。
185 |
186 | ```bash
187 | $ hobbies=( "${activities[@]}" )
188 | ```
189 |
190 | 上面例子中,数组`activities`被拷贝给了另一个数组`hobbies`。
191 |
192 | 这种写法也可以用来为新数组添加成员。
193 |
194 | ```bash
195 | $ hobbies=( "${activities[@]}" diving )
196 | ```
197 |
198 | 上面例子中,新数组`hobbies`在数组`activities`的所有成员之后,又添加了一个成员。
199 |
200 | ### 默认位置
201 |
202 | 如果读取数组成员时,没有读取指定哪一个位置的成员,默认使用`0`号位置。
203 |
204 | ```bash
205 | $ declare -a foo
206 | $ foo=A
207 | $ echo ${foo[0]}
208 | A
209 | ```
210 |
211 | 上面例子中,`foo`是一个数组,赋值的时候不指定位置,实际上是给`foo[0]`赋值。
212 |
213 | 引用一个不带下标的数组变量,则引用的是`0`号位置的数组元素。
214 |
215 | ```bash
216 | $ foo=(a b c d e f)
217 | $ echo ${foo}
218 | a
219 | $ echo $foo
220 | a
221 | ```
222 |
223 | 上面例子中,引用数组元素的时候,没有指定位置,结果返回的是`0`号位置。
224 |
225 | ## 数组的长度
226 |
227 | 要想知道数组的长度(即一共包含多少成员),可以使用下面两种语法。
228 |
229 | ```bash
230 | ${#array[*]}
231 | ${#array[@]}
232 | ```
233 |
234 | 下面是一个例子。
235 |
236 | ```bash
237 | $ a[100]=foo
238 |
239 | $ echo ${#a[*]}
240 | 1
241 |
242 | $ echo ${#a[@]}
243 | 1
244 | ```
245 |
246 | 上面例子中,把字符串赋值给`100`位置的数组元素,这时的数组只有一个元素。
247 |
248 | 注意,如果用这种语法去读取具体的数组成员,就会返回该成员的字符串长度。这一点必须小心。
249 |
250 | ```bash
251 | $ a[100]=foo
252 | $ echo ${#a[100]}
253 | 3
254 | ```
255 |
256 | 上面例子中,`${#a[100]}`实际上是返回数组第100号成员`a[100]`的值(`foo`)的字符串长度。
257 |
258 | ## 提取数组序号
259 |
260 | `${!array[@]}`或`${!array[*]}`,可以返回数组的成员序号,即哪些位置是有值的。
261 |
262 | ```bash
263 | $ arr=([5]=a [9]=b [23]=c)
264 | $ echo ${!arr[@]}
265 | 5 9 23
266 | $ echo ${!arr[*]}
267 | 5 9 23
268 | ```
269 |
270 | 上面例子中,数组的5、9、23号位置有值。
271 |
272 | 利用这个语法,也可以通过`for`循环遍历数组。
273 |
274 | ```bash
275 | arr=(a b c d)
276 |
277 | for i in ${!arr[@]};do
278 | echo ${arr[i]}
279 | done
280 | ```
281 |
282 | ## 提取数组成员
283 |
284 | `${array[@]:position:length}`的语法可以提取数组成员。
285 |
286 | ```bash
287 | $ food=( apples bananas cucumbers dates eggs fajitas grapes )
288 | $ echo ${food[@]:1:1}
289 | bananas
290 | $ echo ${food[@]:1:3}
291 | bananas cucumbers dates
292 | ```
293 |
294 | 上面例子中,`${food[@]:1:1}`返回从数组1号位置开始的1个成员,`${food[@]:1:3}`返回从1号位置开始的3个成员。
295 |
296 | 如果省略长度参数`length`,则返回从指定位置开始的所有成员。
297 |
298 | ```bash
299 | $ echo ${food[@]:4}
300 | eggs fajitas grapes
301 | ```
302 |
303 | 上面例子返回从4号位置开始到结束的所有成员。
304 |
305 | ## 追加数组成员
306 |
307 | 数组末尾追加成员,可以使用`+=`赋值运算符。它能够自动地把值追加到数组末尾。否则,就需要知道数组的最大序号,比较麻烦。
308 |
309 | ```bash
310 | $ foo=(a b c)
311 | $ echo ${foo[@]}
312 | a b c
313 |
314 | $ foo+=(d e f)
315 | $ echo ${foo[@]}
316 | a b c d e f
317 | ```
318 |
319 | ## 删除数组
320 |
321 | 删除一个数组成员,使用`unset`命令。
322 |
323 | ```bash
324 | $ foo=(a b c d e f)
325 | $ echo ${foo[@]}
326 | a b c d e f
327 |
328 | $ unset foo[2]
329 | $ echo ${foo[@]}
330 | a b d e f
331 | ```
332 |
333 | 上面例子中,删除了数组中的第三个元素,下标为2。
334 |
335 | 将某个成员设为空值,可以从返回值中“隐藏”这个成员。
336 |
337 | ```bash
338 | $ foo=(a b c d e f)
339 | $ foo[1]=''
340 | $ echo ${foo[@]}
341 | a c d e f
342 | ```
343 |
344 | 上面例子中,将数组的第二个成员设为空字符串,数组的返回值中,这个成员就“隐藏”了。
345 |
346 | 注意,这里是“隐藏”,而不是删除,因为这个成员仍然存在,只是值变成了空值。
347 |
348 | ```bash
349 | $ foo=(a b c d e f)
350 | $ foo[1]=''
351 | $ echo ${#foo[@]}
352 | 6
353 | $ echo ${!foo[@]}
354 | 0 1 2 3 4 5
355 | ```
356 |
357 | 上面代码中,第二个成员设为空值后,数组仍然包含6个成员。
358 |
359 | 由于空值就是空字符串,所以下面这样写也有隐藏效果,但是不建议这种写法。
360 |
361 | ```bash
362 | $ foo[1]=
363 | ```
364 |
365 | 上面的写法也相当于“隐藏”了数组的第二个成员。
366 |
367 | 直接将数组变量赋值为空字符串,相当于“隐藏”数组的第一个成员。
368 |
369 | ```bash
370 | $ foo=(a b c d e f)
371 | $ foo=''
372 | $ echo ${foo[@]}
373 | b c d e f
374 | ```
375 |
376 | 上面的写法相当于“隐藏”了数组的第一个成员。
377 |
378 | `unset ArrayName`可以清空整个数组。
379 |
380 | ```bash
381 | $ unset ARRAY
382 |
383 | $ echo ${ARRAY[*]}
384 | <--no output-->
385 | ```
386 |
387 | ## 关联数组
388 |
389 | Bash 的新版本支持关联数组。关联数组使用字符串而不是整数作为数组索引。
390 |
391 | `declare -A`可以声明关联数组。
392 |
393 | ```bash
394 | declare -A colors
395 | colors["red"]="#ff0000"
396 | colors["green"]="#00ff00"
397 | colors["blue"]="#0000ff"
398 | ```
399 |
400 | 关联数组必须用带有`-A`选项的`declare`命令声明创建。相比之下,整数索引的数组,可以直接使用变量名创建数组,关联数组就不行。
401 |
402 | 访问关联数组成员的方式,几乎与整数索引数组相同。
403 |
404 | ```bash
405 | echo ${colors["blue"]}
406 | ```
407 |
408 |
--------------------------------------------------------------------------------
/docs/condition.md:
--------------------------------------------------------------------------------
1 | # 条件判断
2 |
3 | 本章介绍 Bash 脚本的条件判断语法。
4 |
5 | ## if 结构
6 |
7 | `if`是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下。
8 |
9 | ```bash
10 | if commands; then
11 | commands
12 | [elif commands; then
13 | commands...]
14 | [else
15 | commands]
16 | fi
17 | ```
18 |
19 | 这个命令分成三个部分:`if`、`elif`和`else`。其中,后两个部分是可选的。
20 |
21 | `if`关键字后面是主要的判断条件,`elif`用来添加在主条件不成立时的其他判断条件,`else`则是所有条件都不成立时要执行的部分。
22 |
23 | ```bash
24 | if test $USER = "foo"; then
25 | echo "Hello foo."
26 | else
27 | echo "You are not foo."
28 | fi
29 | ```
30 |
31 | 上面的例子中,判断条件是环境变量`$USER`是否等于`foo`,如果等于就输出`Hello foo.`,否则输出其他内容。
32 |
33 | `if`和`then`写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。
34 |
35 | ```bash
36 | if true
37 | then
38 | echo 'hello world'
39 | fi
40 |
41 | if false
42 | then
43 | echo 'it is false' # 本行不会执行
44 | fi
45 | ```
46 |
47 | 上面的例子中,`true`和`false`是两个特殊命令,前者代表操作成功,后者代表操作失败。`if true`意味着命令部分总是会执行,`if false`意味着命令部分永远不会执行。
48 |
49 | 除了多行的写法,`if`结构也可以写成单行。
50 |
51 | ```bash
52 | $ if true; then echo 'hello world'; fi
53 | hello world
54 |
55 | $ if false; then echo "It's true."; fi
56 | ```
57 |
58 | 注意,`if`关键字后面也可以是一条命令,该条命令执行成功(返回值`0`),就意味着判断条件成立。
59 |
60 | ```bash
61 | $ if echo 'hi'; then echo 'hello world'; fi
62 | hi
63 | hello world
64 | ```
65 |
66 | 上面命令中,`if`后面是一条命令`echo 'hi'`。该命令会执行,如果返回值是`0`,则执行`then`的部分。
67 |
68 | `if`后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回`0`,就会执行`then`的部分。
69 |
70 | ```bash
71 | $ if false; true; then echo 'hello world'; fi
72 | hello world
73 | ```
74 |
75 | 上面例子中,`if`后面有两条命令(`false;true;`),第二条命令(`true`)决定了`then`的部分是否会执行。
76 |
77 | `elif`部分可以有多个。
78 |
79 | ```bash
80 | #!/bin/bash
81 |
82 | echo -n "输入一个1到3之间的数字(包含两端)> "
83 | read character
84 | if [ "$character" = "1" ]; then
85 | echo 1
86 | elif [ "$character" = "2" ]; then
87 | echo 2
88 | elif [ "$character" = "3" ]; then
89 | echo 3
90 | else
91 | echo 输入不符合要求
92 | fi
93 | ```
94 |
95 | 上面例子中,如果用户输入`3`,就会连续判断3次。
96 |
97 | ## test 命令
98 |
99 | `if`结构的判断条件,一般使用`test`命令,有三种形式。
100 |
101 | ```bash
102 | # 写法一
103 | test expression
104 |
105 | # 写法二
106 | [ expression ]
107 |
108 | # 写法三
109 | [[ expression ]]
110 | ```
111 |
112 | 上面三种形式是等价的,但是第三种形式还支持正则判断,前两种不支持。
113 |
114 | 上面的`expression`是一个表达式。这个表达式为真,`test`命令执行成功(返回值为`0`);表达式为伪,`test`命令执行失败(返回值为`1`)。注意,第二种和第三种写法,`[`和`]`与内部的表达式之间必须有空格。
115 |
116 | ```bash
117 | $ test -f /etc/hosts
118 | $ echo $?
119 | 0
120 |
121 | $ [ -f /etc/hosts ]
122 | $ echo $?
123 | 0
124 | ```
125 |
126 | 上面的例子中,`test`命令采用两种写法,判断`/etc/hosts`文件是否存在,这两种写法是等价的。命令执行后,返回值为`0`,表示该文件确实存在。
127 |
128 | 实际上,`[`这个字符是`test`命令的一种简写形式,可以看作是一个独立的命令,这解释了为什么它后面必须有空格。
129 |
130 | 下面把`test`命令的三种形式,用在`if`结构中,判断一个文件是否存在。
131 |
132 | ```bash
133 | # 写法一
134 | if test -e /tmp/foo.txt ; then
135 | echo "Found foo.txt"
136 | fi
137 |
138 | # 写法二
139 | if [ -e /tmp/foo.txt ] ; then
140 | echo "Found foo.txt"
141 | fi
142 |
143 | # 写法三
144 | if [[ -e /tmp/foo.txt ]] ; then
145 | echo "Found foo.txt"
146 | fi
147 | ```
148 |
149 | ## 判断表达式
150 |
151 | `if`关键字后面,跟的是一个命令。这个命令可以是`test`命令,也可以是其他命令。命令的返回值为`0`表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。
152 |
153 | 常用的判断表达式有下面这些。
154 |
155 | ### 文件判断
156 |
157 | 以下表达式用来判断文件状态。
158 |
159 | - `[ -b file ]`:如果 file 存在并且是一个块(设备)文件,则为`true`。
160 | - `[ -c file ]`:如果 file 存在并且是一个字符(设备)文件,则为`true`。
161 | - `[ -d file ]`:如果 file 存在并且是一个目录,则为`true`。
162 | - `[ -e file ]`:如果 file 存在,则为`true`。
163 | - `[ -f file ]`:如果 file 存在并且是一个普通文件,则为`true`。
164 | - `[ -g file ]`:如果 file 存在并且设置了组 ID,则为`true`。
165 | - `[ -G file ]`:如果 file 存在并且属于有效的组 ID,则为`true`。
166 | - `[ -h file ]`:如果 file 存在并且是符号链接,则为`true`。
167 | - `[ -k file ]`:如果 file 存在并且设置了它的“sticky bit”,则为`true`。
168 | - `[ -L file ]`:如果 file 存在并且是一个符号链接,则为`true`。
169 | - `[ -N file ]`:如果 file 存在并且自上次读取后已被修改,则为`true`。
170 | - `[ -O file ]`:如果 file 存在并且属于有效的用户 ID,则为`true`。
171 | - `[ -p file ]`:如果 file 存在并且是一个命名管道,则为`true`。
172 | - `[ -r file ]`:如果 file 存在并且可读(当前用户有可读权限),则为`true`。
173 | - `[ -s file ]`:如果 file 存在且其长度大于零,则为`true`。
174 | - `[ -S file ]`:如果 file 存在且是一个网络 socket,则为`true`。
175 | - `[ -t fd ]`:如果 fd 是一个文件描述符,并且重定向到终端,则为`true`。 这可以用来判断是否重定向了标准输入/输出/错误。
176 | - `[ -u file ]`:如果 file 存在并且设置了 setuid 位,则为`true`。
177 | - `[ -w file ]`:如果 file 存在并且可写(当前用户拥有可写权限),则为`true`。
178 | - `[ -x file ]`:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为`true`。
179 | - `[ FILE1 -nt FILE2 ]`:如果 FILE1 比 FILE2 的更新时间更近,或者 FILE1 存在而 FILE2 不存在,则为`true`。
180 | - `[ FILE1 -ot FILE2 ]`:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为`true`。
181 | - `[ FILE1 -ef FILE2 ]`:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为`true`。
182 |
183 | 下面是一个示例。
184 |
185 | ```bash
186 | #!/bin/bash
187 |
188 | FILE=~/.bashrc
189 |
190 | if [ -e "$FILE" ]; then
191 | if [ -f "$FILE" ]; then
192 | echo "$FILE is a regular file."
193 | fi
194 | if [ -d "$FILE" ]; then
195 | echo "$FILE is a directory."
196 | fi
197 | if [ -r "$FILE" ]; then
198 | echo "$FILE is readable."
199 | fi
200 | if [ -w "$FILE" ]; then
201 | echo "$FILE is writable."
202 | fi
203 | if [ -x "$FILE" ]; then
204 | echo "$FILE is executable/searchable."
205 | fi
206 | else
207 | echo "$FILE does not exist"
208 | exit 1
209 | fi
210 | ```
211 |
212 | 上面代码中,`$FILE`要放在双引号之中,这样可以防止变量`$FILE`为空,从而出错。因为`$FILE`如果为空,这时`[ -e $FILE ]`就变成`[ -e ]`,这会被判断为真。而`$FILE`放在双引号之中,`[ -e "$FILE" ]`就变成`[ -e "" ]`,这会被判断为伪。
213 |
214 | ### 字符串判断
215 |
216 | 以下表达式用来判断字符串。
217 |
218 | - `[ string ]`:如果`string`不为空(长度大于0),则判断为真。
219 | - `[ -n string ]`:如果字符串`string`的长度大于零,则判断为真。
220 | - `[ -z string ]`:如果字符串`string`的长度为零,则判断为真。
221 | - `[ string1 = string2 ]`:如果`string1`和`string2`相同,则判断为真。
222 | - `[ string1 == string2 ]` 等同于`[ string1 = string2 ]`。
223 | - `[ string1 != string2 ]`:如果`string1`和`string2`不相同,则判断为真。
224 | - `[ string1 '>' string2 ]`:如果按照字典顺序`string1`排列在`string2`之后,则判断为真。
225 | - `[ string1 '<' string2 ]`:如果按照字典顺序`string1`排列在`string2`之前,则判断为真。
226 |
227 | 注意,`test`命令内部的`>`和`<`,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。
228 |
229 | 下面是一个示例。
230 |
231 | ```bash
232 | #!/bin/bash
233 |
234 | ANSWER=maybe
235 |
236 | if [ -z "$ANSWER" ]; then
237 | echo "There is no answer." >&2
238 | exit 1
239 | fi
240 | if [ "$ANSWER" = "yes" ]; then
241 | echo "The answer is YES."
242 | elif [ "$ANSWER" = "no" ]; then
243 | echo "The answer is NO."
244 | elif [ "$ANSWER" = "maybe" ]; then
245 | echo "The answer is MAYBE."
246 | else
247 | echo "The answer is UNKNOWN."
248 | fi
249 | ```
250 |
251 | 上面代码中,首先确定`$ANSWER`字符串是否为空。如果为空,就终止脚本,并把退出状态设为`1`。注意,这里的`echo`命令把错误信息`There is no answer.`重定向到标准错误,这是处理错误信息的常用方法。如果`$ANSWER`字符串不为空,就判断它的值是否等于`yes`、`no`或者`maybe`。
252 |
253 | 注意,字符串判断时,变量要放在双引号之中,比如`[ -n "$COUNT" ]`,否则变量替换成字符串以后,`test`命令可能会报错,提示参数过多。另外,如果不放在双引号之中,变量为空时,命令会变成`[ -n ]`,这时会判断为真。如果放在双引号之中,`[ -n "" ]`就判断为伪。
254 |
255 | ### 整数判断
256 |
257 | 下面的表达式用于判断整数。
258 |
259 | - `[ integer1 -eq integer2 ]`:如果`integer1`等于`integer2`,则为`true`。
260 | - `[ integer1 -ne integer2 ]`:如果`integer1`不等于`integer2`,则为`true`。
261 | - `[ integer1 -le integer2 ]`:如果`integer1`小于或等于`integer2`,则为`true`。
262 | - `[ integer1 -lt integer2 ]`:如果`integer1`小于`integer2`,则为`true`。
263 | - `[ integer1 -ge integer2 ]`:如果`integer1`大于或等于`integer2`,则为`true`。
264 | - `[ integer1 -gt integer2 ]`:如果`integer1`大于`integer2`,则为`true`。
265 |
266 | 下面是一个用法的例子。
267 |
268 | ```bash
269 | #!/bin/bash
270 |
271 | INT=-5
272 |
273 | if [ -z "$INT" ]; then
274 | echo "INT is empty." >&2
275 | exit 1
276 | fi
277 | if [ $INT -eq 0 ]; then
278 | echo "INT is zero."
279 | else
280 | if [ $INT -lt 0 ]; then
281 | echo "INT is negative."
282 | else
283 | echo "INT is positive."
284 | fi
285 | if [ $((INT % 2)) -eq 0 ]; then
286 | echo "INT is even."
287 | else
288 | echo "INT is odd."
289 | fi
290 | fi
291 | ```
292 |
293 | 上面例子中,先判断变量`$INT`是否为空,然后判断是否为`0`,接着判断正负,最后通过求余数判断奇偶。
294 |
295 | ### 正则判断
296 |
297 | `[[ expression ]]`这种判断形式,支持正则表达式。
298 |
299 | ```bash
300 | [[ string1 =~ regex ]]
301 | ```
302 |
303 | 上面的语法中,`regex`是一个正则表示式,`=~`是正则比较运算符。
304 |
305 | 下面是一个例子。
306 |
307 | ```bash
308 | #!/bin/bash
309 |
310 | INT=-5
311 |
312 | if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
313 | echo "INT is an integer."
314 | exit 0
315 | else
316 | echo "INT is not an integer." >&2
317 | exit 1
318 | fi
319 | ```
320 |
321 | 上面代码中,先判断变量`INT`的字符串形式,是否满足`^-?[0-9]+$`的正则模式,如果满足就表明它是一个整数。
322 |
323 | ### test 判断的逻辑运算
324 |
325 | 通过逻辑运算,可以把多个`test`判断表达式结合起来,创造更复杂的判断。三种逻辑运算`AND`,`OR`,和`NOT`,都有自己的专用符号。
326 |
327 | - `AND`运算:符号`&&`,也可使用参数`-a`。
328 | - `OR`运算:符号`||`,也可使用参数`-o`。
329 | - `NOT`运算:符号`!`。
330 |
331 | 下面是一个`AND`的例子,判断整数是否在某个范围之内。
332 |
333 | ```bash
334 | #!/bin/bash
335 |
336 | MIN_VAL=1
337 | MAX_VAL=100
338 |
339 | INT=50
340 |
341 | if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
342 | if [[ $INT -ge $MIN_VAL && $INT -le $MAX_VAL ]]; then
343 | echo "$INT is within $MIN_VAL to $MAX_VAL."
344 | else
345 | echo "$INT is out of range."
346 | fi
347 | else
348 | echo "INT is not an integer." >&2
349 | exit 1
350 | fi
351 | ```
352 |
353 | 上面例子中,`&&`用来连接两个判断条件:大于等于`$MIN_VAL`,并且小于等于`$MAX_VAL`。
354 |
355 | 使用否定操作符`!`时,最好用圆括号确定转义的范围。
356 |
357 | ```bash
358 | if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ]; then
359 | echo "$INT is outside $MIN_VAL to $MAX_VAL."
360 | else
361 | echo "$INT is in range."
362 | fi
363 | ```
364 |
365 | 上面例子中,`test`命令内部使用的圆括号,必须使用引号或者转义,否则会被 Bash 解释。
366 |
367 | 使用`-a`连接两个判断条件不太直观,一般推荐使用`&&`代替,上面的脚本可以改写成下面这样。
368 |
369 | ```bash
370 | if !([ $INT -ge $MIN_VAL ] && [ $INT -le $MAX_VAL ]); then
371 | echo "$INT is outside $MIN_VAL to $MAX_VAL."
372 | else
373 | echo "$INT is in range."
374 | fi
375 | ```
376 |
377 | ### 算术判断
378 |
379 | Bash 还提供了`((...))`作为算术条件,进行算术运算的判断。
380 |
381 | ```bash
382 | if ((3 > 2)); then
383 | echo "true"
384 | fi
385 | ```
386 |
387 | 上面代码执行后,会打印出`true`。
388 |
389 | 注意,算术判断不需要使用`test`命令,而是直接使用`((...))`结构。这个结构的返回值,决定了判断的真伪。
390 |
391 | 如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心。
392 |
393 | ```bash
394 | $ if ((1)); then echo "It is true."; fi
395 | It is true.
396 | $ if ((0)); then echo "It is true."; else echo "it is false."; fi
397 | It is false.
398 | ```
399 |
400 | 上面例子中,`((1))`表示判断成立,`((0))`表示判断不成立。
401 |
402 | 算术条件`((...))`也可以用于变量赋值。
403 |
404 | ```bash
405 | $ if (( foo = 5 ));then echo "foo is $foo"; fi
406 | foo is 5
407 | ```
408 |
409 | 上面例子中,`(( foo = 5 ))`完成了两件事情。首先把`5`赋值给变量`foo`,然后根据返回值`5`,判断条件为真。
410 |
411 | 注意,赋值语句返回等号右边的值,如果返回的是`0`,则判断为假。
412 |
413 | ```bash
414 | $ if (( foo = 0 ));then echo "It is true.";else echo "It is false."; fi
415 | It is false.
416 | ```
417 |
418 | 下面是用算术条件改写的数值判断脚本。
419 |
420 | ```bash
421 | #!/bin/bash
422 |
423 | INT=-5
424 |
425 | if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
426 | if ((INT == 0)); then
427 | echo "INT is zero."
428 | else
429 | if ((INT < 0)); then
430 | echo "INT is negative."
431 | else
432 | echo "INT is positive."
433 | fi
434 | if (( ((INT % 2)) == 0)); then
435 | echo "INT is even."
436 | else
437 | echo "INT is odd."
438 | fi
439 | fi
440 | else
441 | echo "INT is not an integer." >&2
442 | exit 1
443 | fi
444 | ```
445 |
446 | 只要是算术表达式,都能用于`((...))`语法,详见《Bash 的算术运算》一章。
447 |
448 | ### 普通命令的逻辑运算
449 |
450 | 如果`if`结构使用的不是`test`命令,而是普通命令,比如上一节的`((...))`算术运算,或者`test`命令与普通命令混用,那么可以使用 Bash 的命令控制操作符`&&`(AND)和`||`(OR),进行多个命令的逻辑运算。
451 |
452 | ```bash
453 | $ command1 && command2
454 | $ command1 || command2
455 | ```
456 |
457 | 对于`&&`操作符,先执行`command1`,只有`command1`执行成功后, 才会执行`command2`。对于`||`操作符,先执行`command1`,只有`command1`执行失败后, 才会执行`command2`。
458 |
459 | ```bash
460 | $ mkdir temp && cd temp
461 | ```
462 |
463 | 上面的命令会创建一个名为`temp`的目录,执行成功后,才会执行第二个命令,进入这个目录。
464 |
465 | ```bash
466 | $ [ -d temp ] || mkdir temp
467 | ```
468 |
469 | 上面的命令会测试目录`temp`是否存在,如果不存在,就会执行第二个命令,创建这个目录。这种写法非常有助于在脚本中处理错误。
470 |
471 | ```bash
472 | [ ! -d temp ] && exit 1
473 | ```
474 |
475 | 上面的命令中,如果`temp`子目录不存在,脚本会终止,并且返回值为`1`。
476 |
477 | 下面就是`if`与`&&`结合使用的写法。
478 |
479 | ```bash
480 | if [ condition ] && [ condition ]; then
481 | command
482 | fi
483 | ```
484 |
485 | 下面是一个示例。
486 |
487 | ```bash
488 | #! /bin/bash
489 |
490 | filename=$1
491 | word1=$2
492 | word2=$3
493 |
494 | if grep $word1 $filename && grep $word2 $filename
495 | then
496 | echo "$word1 and $word2 are both in $filename."
497 | fi
498 | ```
499 |
500 | 上面的例子只有在指定文件里面,同时存在搜索词`word1`和`word2`,就会执行`if`的命令部分。
501 |
502 | 下面的示例演示如何将一个`&&`判断表达式,改写成对应的`if`结构。
503 |
504 | ```bash
505 | [[ -d "$dir_name" ]] && cd "$dir_name" && rm *
506 |
507 | # 等同于
508 |
509 | if [[ ! -d "$dir_name" ]]; then
510 | echo "No such directory: '$dir_name'" >&2
511 | exit 1
512 | fi
513 | if ! cd "$dir_name"; then
514 | echo "Cannot cd to '$dir_name'" >&2
515 | exit 1
516 | fi
517 | if ! rm *; then
518 | echo "File deletion failed. Check results" >&2
519 | exit 1
520 | fi
521 | ```
522 |
523 | ## case 结构
524 |
525 | `case`结构用于多值判断,可以为每个值指定对应的命令,跟包含多个`elif`的`if`结构等价,但是语义更好。它的语法如下。
526 |
527 | ```bash
528 | case expression in
529 | pattern )
530 | commands ;;
531 | pattern )
532 | commands ;;
533 | ...
534 | esac
535 | ```
536 |
537 | 上面代码中,`expression`是一个表达式,`pattern`是表达式的值或者一个模式,可以有多条,用来匹配多个值,每条以两个分号(`;`)结尾。
538 |
539 | ```bash
540 | #!/bin/bash
541 |
542 | echo -n "输入一个1到3之间的数字(包含两端)> "
543 | read character
544 | case $character in
545 | 1 ) echo 1
546 | ;;
547 | 2 ) echo 2
548 | ;;
549 | 3 ) echo 3
550 | ;;
551 | * ) echo 输入不符合要求
552 | esac
553 | ```
554 |
555 | 上面例子中,最后一条匹配语句的模式是`*`,这个通配符可以匹配其他字符和没有输入字符的情况,类似`if`的`else`部分。
556 |
557 | 下面是另一个例子。
558 |
559 | ```bash
560 | #!/bin/bash
561 |
562 | OS=$(uname -s)
563 |
564 | case "$OS" in
565 | FreeBSD) echo "This is FreeBSD" ;;
566 | Darwin) echo "This is Mac OSX" ;;
567 | AIX) echo "This is AIX" ;;
568 | Minix) echo "This is Minix" ;;
569 | Linux) echo "This is Linux" ;;
570 | *) echo "Failed to identify this OS" ;;
571 | esac
572 | ```
573 |
574 | 上面的例子判断当前是什么操作系统。
575 |
576 | `case`的匹配模式可以使用各种通配符,下面是一些例子。
577 |
578 | - `a)`:匹配`a`。
579 | - `a|b)`:匹配`a`或`b`。
580 | - `[[:alpha:]])`:匹配单个字母。
581 | - `???)`:匹配3个字符的单词。
582 | - `*.txt)`:匹配`.txt`结尾。
583 | - `*)`:匹配任意输入,通常作为`case`结构的最后一个模式。
584 |
585 | ```bash
586 | #!/bin/bash
587 |
588 | echo -n "输入一个字母或数字 > "
589 | read character
590 | case $character in
591 | [[:lower:]] | [[:upper:]] ) echo "输入了字母 $character"
592 | ;;
593 | [0-9] ) echo "输入了数字 $character"
594 | ;;
595 | * ) echo "输入不符合要求"
596 | esac
597 | ```
598 |
599 | 上面例子中,使用通配符`[[:lower:]] | [[:upper:]]`匹配字母,`[0-9]`匹配数字。
600 |
601 | Bash 4.0之前,`case`结构只能匹配一个条件,然后就会退出`case`结构。Bash 4.0之后,允许匹配多个条件,这时可以用`;;&`终止每个条件块。
602 |
603 | ```bash
604 | #!/bin/bash
605 | # test.sh
606 |
607 | read -n 1 -p "Type a character > "
608 | echo
609 | case $REPLY in
610 | [[:upper:]]) echo "'$REPLY' is upper case." ;;&
611 | [[:lower:]]) echo "'$REPLY' is lower case." ;;&
612 | [[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
613 | [[:digit:]]) echo "'$REPLY' is a digit." ;;&
614 | [[:graph:]]) echo "'$REPLY' is a visible character." ;;&
615 | [[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
616 | [[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
617 | [[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
618 | esac
619 | ```
620 |
621 | 执行上面的脚本,会得到下面的结果。
622 |
623 | ```bash
624 | $ test.sh
625 | Type a character > a
626 | 'a' is lower case.
627 | 'a' is alphabetic.
628 | 'a' is a visible character.
629 | 'a' is a hexadecimal digit.
630 | ```
631 |
632 | 可以看到条件语句结尾添加了`;;&`以后,在匹配一个条件之后,并没有退出`case`结构,而是继续判断下一个条件。
633 |
634 | ## 参考链接
635 |
636 | - [The Linux Command Line](http://linuxcommand.org/tlcl.php), William Shotts
637 |
638 |
--------------------------------------------------------------------------------
/docs/debug.md:
--------------------------------------------------------------------------------
1 | # 脚本除错
2 |
3 | 本章介绍如何对 Shell 脚本除错。
4 |
5 | ## 常见错误
6 |
7 | 编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很容易出错。
8 |
9 | ```bash
10 | #! /bin/bash
11 |
12 | dir_name=/path/not/exist
13 |
14 | cd $dir_name
15 | rm *
16 | ```
17 |
18 | 上面脚本中,如果目录`$dir_name`不存在,`cd $dir_name`命令就会执行失败。这时,就不会改变当前目录,脚本会继续执行下去,导致`rm *`命令删光当前目录的文件。
19 |
20 | 如果改成下面的样子,也会有问题。
21 |
22 | ```bash
23 | cd $dir_name && rm *
24 | ```
25 |
26 | 上面脚本中,只有`cd $dir_name`执行成功,才会执行`rm *`。但是,如果变量`$dir_name`为空,`cd`就会进入用户主目录,从而删光用户主目录的文件。
27 |
28 | 下面的写法才是正确的。
29 |
30 | ```bash
31 | [[ -d $dir_name ]] && cd $dir_name && rm *
32 | ```
33 |
34 | 上面代码中,先判断目录`$dir_name`是否存在,然后才执行其他操作。
35 |
36 | 如果不放心删除什么文件,可以先打印出来看一下。
37 |
38 | ```bash
39 | [[ -d $dir_name ]] && cd $dir_name && echo rm *
40 | ```
41 |
42 | 上面命令中,`echo rm *`不会删除文件,只会打印出来要删除的文件。
43 |
44 | ## `bash`的`-x`参数
45 |
46 | `bash`的`-x`参数可以在执行每一行命令之前,打印该命令。一旦出错,这样就比较容易追查。
47 |
48 | 下面是一个脚本`script.sh`。
49 |
50 | ```bash
51 | # script.sh
52 | echo hello world
53 | ```
54 |
55 | 加上`-x`参数,执行每条命令之前,都会显示该命令。
56 |
57 | ```bash
58 | $ bash -x script.sh
59 | + echo hello world
60 | hello world
61 | ```
62 |
63 | 上面例子中,行首为`+`的行,显示该行是所要执行的命令,下一行才是该命令的执行结果。
64 |
65 | 下面再看一个`-x`写在脚本内部的例子。
66 |
67 | ```bash
68 | #! /bin/bash -x
69 | # trouble: script to demonstrate common errors
70 |
71 | number=1
72 | if [ $number = 1 ]; then
73 | echo "Number is equal to 1."
74 | else
75 | echo "Number is not equal to 1."
76 | fi
77 | ```
78 |
79 | 上面的脚本执行之后,会输出每一行命令。
80 |
81 | ```bash
82 | $ trouble
83 | + number=1
84 | + '[' 1 = 1 ']'
85 | + echo 'Number is equal to 1.'
86 | Number is equal to 1.
87 | ```
88 |
89 | 输出的命令之前的`+`号,是由系统变量`PS4`决定,可以修改这个变量。
90 |
91 | ```bash
92 | $ export PS4='$LINENO + '
93 | $ trouble
94 | 5 + number=1
95 | 7 + '[' 1 = 1 ']'
96 | 8 + echo 'Number is equal to 1.'
97 | Number is equal to 1.
98 | ```
99 |
100 | 另外,`set`命令也可以设置 Shell 的行为参数,有利于脚本除错,详见《set 命令》一章。
101 |
102 | ## 环境变量
103 |
104 | 有一些环境变量常用于除错。
105 |
106 | ### LINENO
107 |
108 | 变量`LINENO`返回它在脚本里面的行号。
109 |
110 | ```bash
111 | #!/bin/bash
112 |
113 | echo "This is line $LINENO"
114 | ```
115 |
116 | 执行上面的脚本`test.sh`,`$LINENO`会返回`3`。
117 |
118 | ```bash
119 | $ ./test.sh
120 | This is line 3
121 | ```
122 |
123 | ### FUNCNAME
124 |
125 | 变量`FUNCNAME`返回一个数组,内容是当前的函数调用堆栈。该数组的0号成员是当前调用的函数,1号成员是调用当前函数的函数,以此类推。
126 |
127 | ```bash
128 | #!/bin/bash
129 |
130 | function func1()
131 | {
132 | echo "func1: FUNCNAME0 is ${FUNCNAME[0]}"
133 | echo "func1: FUNCNAME1 is ${FUNCNAME[1]}"
134 | echo "func1: FUNCNAME2 is ${FUNCNAME[2]}"
135 | func2
136 | }
137 |
138 | function func2()
139 | {
140 | echo "func2: FUNCNAME0 is ${FUNCNAME[0]}"
141 | echo "func2: FUNCNAME1 is ${FUNCNAME[1]}"
142 | echo "func2: FUNCNAME2 is ${FUNCNAME[2]}"
143 | }
144 |
145 | func1
146 | ```
147 |
148 | 执行上面的脚本`test.sh`,结果如下。
149 |
150 | ```bash
151 | $ ./test.sh
152 | func1: FUNCNAME0 is func1
153 | func1: FUNCNAME1 is main
154 | func1: FUNCNAME2 is
155 | func2: FUNCNAME0 is func2
156 | func2: FUNCNAME1 is func1
157 | func2: FUNCNAME2 is main
158 | ```
159 |
160 | 上面例子中,执行`func1`时,变量`FUNCNAME`的0号成员是`func1`,1号成员是调用`func1`的主脚本`main`。执行`func2`时,变量`FUNCNAME`的0号成员是`func2`,1号成员是调用`func2`的`func1`。
161 |
162 | ### BASH_SOURCE
163 |
164 | 变量`BASH_SOURCE`返回一个数组,内容是当前的脚本调用堆栈。该数组的0号成员是当前执行的脚本,1号成员是调用当前脚本的脚本,以此类推,跟变量`FUNCNAME`是一一对应关系。
165 |
166 | 下面有两个子脚本`lib1.sh`和`lib2.sh`。
167 |
168 | ```bash
169 | # lib1.sh
170 | function func1()
171 | {
172 | echo "func1: BASH_SOURCE0 is ${BASH_SOURCE[0]}"
173 | echo "func1: BASH_SOURCE1 is ${BASH_SOURCE[1]}"
174 | echo "func1: BASH_SOURCE2 is ${BASH_SOURCE[2]}"
175 | func2
176 | }
177 | ```
178 |
179 | ```bash
180 | # lib2.sh
181 | function func2()
182 | {
183 | echo "func2: BASH_SOURCE0 is ${BASH_SOURCE[0]}"
184 | echo "func2: BASH_SOURCE1 is ${BASH_SOURCE[1]}"
185 | echo "func2: BASH_SOURCE2 is ${BASH_SOURCE[2]}"
186 | }
187 | ```
188 |
189 | 然后,主脚本`main.sh`调用上面两个子脚本。
190 |
191 | ```bash
192 | #!/bin/bash
193 | # main.sh
194 |
195 | source lib1.sh
196 | source lib2.sh
197 |
198 | func1
199 | ```
200 |
201 | 执行主脚本`main.sh`,会得到下面的结果。
202 |
203 | ```bash
204 | $ ./main.sh
205 | func1: BASH_SOURCE0 is lib1.sh
206 | func1: BASH_SOURCE1 is ./main.sh
207 | func1: BASH_SOURCE2 is
208 | func2: BASH_SOURCE0 is lib2.sh
209 | func2: BASH_SOURCE1 is lib1.sh
210 | func2: BASH_SOURCE2 is ./main.sh
211 | ```
212 |
213 | 上面例子中,执行函数`func1`时,变量`BASH_SOURCE`的0号成员是`func1`所在的脚本`lib1.sh`,1号成员是主脚本`main.sh`;执行函数`func2`时,变量`BASH_SOURCE`的0号成员是`func2`所在的脚本`lib2.sh`,1号成员是调用`func2`的脚本`lib1.sh`。
214 |
215 | ### BASH_LINENO
216 |
217 | 变量`BASH_LINENO`返回一个数组,内容是每一轮调用对应的行号。`${BASH_LINENO[$i]}`跟`${FUNCNAME[$i]}`是一一对应关系,表示`${FUNCNAME[$i]}`在调用它的脚本文件`${BASH_SOURCE[$i+1]}`里面的行号。
218 |
219 | 下面有两个子脚本`lib1.sh`和`lib2.sh`。
220 |
221 | ```bash
222 | # lib1.sh
223 | function func1()
224 | {
225 | echo "func1: BASH_LINENO is ${BASH_LINENO[0]}"
226 | echo "func1: FUNCNAME is ${FUNCNAME[0]}"
227 | echo "func1: BASH_SOURCE is ${BASH_SOURCE[1]}"
228 |
229 | func2
230 | }
231 | ```
232 |
233 | ```bash
234 | # lib2.sh
235 | function func2()
236 | {
237 | echo "func2: BASH_LINENO is ${BASH_LINENO[0]}"
238 | echo "func2: FUNCNAME is ${FUNCNAME[0]}"
239 | echo "func2: BASH_SOURCE is ${BASH_SOURCE[1]}"
240 | }
241 | ```
242 |
243 | 然后,主脚本`main.sh`调用上面两个子脚本。
244 |
245 | ```bash
246 | #!/bin/bash
247 | # main.sh
248 |
249 | source lib1.sh
250 | source lib2.sh
251 |
252 | func1
253 | ```
254 |
255 | 执行主脚本`main.sh`,会得到下面的结果。
256 |
257 | ```bash
258 | $ ./main.sh
259 | func1: BASH_LINENO is 7
260 | func1: FUNCNAME is func1
261 | func1: BASH_SOURCE is main.sh
262 | func2: BASH_LINENO is 8
263 | func2: FUNCNAME is func2
264 | func2: BASH_SOURCE is lib1.sh
265 | ```
266 |
267 | 上面例子中,函数`func1`是在`main.sh`的第7行调用,函数`func2`是在`lib1.sh`的第8行调用的。
268 |
269 |
--------------------------------------------------------------------------------
/docs/function.md:
--------------------------------------------------------------------------------
1 | # Bash 函数
2 |
3 | 本章介绍 Bash 函数的用法。
4 |
5 | ## 简介
6 |
7 | 函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令。
8 |
9 | 函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。
10 |
11 | Bash 函数定义的语法有两种。
12 |
13 | ```bash
14 | # 第一种
15 | fn() {
16 | # codes
17 | }
18 |
19 | # 第二种
20 | function fn() {
21 | # codes
22 | }
23 | ```
24 |
25 | 上面代码中,`fn`是自定义的函数名,函数代码就写在大括号之中。这两种写法是等价的。
26 |
27 | 下面是一个简单函数的例子。
28 |
29 | ```bash
30 | hello() {
31 | echo "Hello $1"
32 | }
33 | ```
34 |
35 | 上面代码中,函数体里面的`$1`表示函数调用时的第一个参数。
36 |
37 | 调用时,就直接写函数名,参数跟在函数名后面。
38 |
39 | ```bash
40 | $ hello world
41 | Hello world
42 | ```
43 |
44 | 下面是一个多行函数的例子,显示当前日期时间。
45 |
46 | ```bash
47 | today() {
48 | echo -n "Today's date is: "
49 | date +"%A, %B %-d, %Y"
50 | }
51 | ```
52 |
53 | 删除一个函数,可以使用`unset`命令。
54 |
55 | ```bash
56 | unset -f functionName
57 | ```
58 |
59 | 查看当前 Shell 已经定义的所有函数,可以使用`declare`命令。
60 |
61 | ```bash
62 | $ declare -f
63 | ```
64 |
65 | 上面的`declare`命令不仅会输出函数名,还会输出所有定义。输出顺序是按照函数名的字母表顺序。由于会输出很多内容,最好通过管道命令配合`more`或`less`使用。
66 |
67 | `declare`命令还支持查看单个函数的定义。
68 |
69 | ```bash
70 | $ declare -f functionName
71 | ```
72 |
73 | `declare -F`可以输出所有已经定义的函数名,不含函数体。
74 |
75 | ```bash
76 | $ declare -F
77 | ```
78 |
79 | ## 参数变量
80 |
81 | 函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的。
82 |
83 | - `$1`~`$9`:函数的第一个到第9个的参数。
84 | - `$0`:函数所在的脚本名。
85 | - `$#`:函数的参数总数。
86 | - `$@`:函数的全部参数,参数之间使用空格分隔。
87 | - `$*`:函数的全部参数,参数之间使用变量`$IFS`值的第一个字符分隔,默认为空格,但是可以自定义。
88 |
89 | 如果函数的参数多于9个,那么第10个参数可以用`${10}`的形式引用,以此类推。
90 |
91 | 下面是一个示例脚本`test.sh`。
92 |
93 | ```bash
94 | #!/bin/bash
95 | # test.sh
96 |
97 | function alice {
98 | echo "alice: $@"
99 | echo "$0: $1 $2 $3 $4"
100 | echo "$# arguments"
101 |
102 | }
103 |
104 | alice in wonderland
105 | ```
106 |
107 | 运行该脚本,结果如下。
108 |
109 | ```bash
110 | $ bash test.sh
111 | alice: in wonderland
112 | test.sh: in wonderland
113 | 2 arguments
114 | ```
115 |
116 | 上面例子中,由于函数`alice`只有第一个和第二个参数,所以第三个和第四个参数为空。
117 |
118 | 下面是一个日志函数的例子。
119 |
120 | ```bash
121 | function log_msg {
122 | echo "[`date '+ %F %T'` ]: $@"
123 | }
124 | ```
125 |
126 | 使用方法如下。
127 |
128 | ```bash
129 | $ log_msg "This is sample log message"
130 | [ 2018-08-16 19:56:34 ]: This is sample log message
131 | ```
132 |
133 | ## return 命令
134 |
135 | `return`命令用于从函数返回一个值。函数执行到这条命令,就不再往下执行了,直接返回了。
136 |
137 | ```bash
138 | function func_return_value {
139 | return 10
140 | }
141 | ```
142 |
143 | 函数将返回值返回给调用者。如果命令行直接执行函数,下一个命令可以用`$?`拿到返回值。
144 |
145 | ```bash
146 | $ func_return_value
147 | $ echo "Value returned by function is: $?"
148 | Value returned by function is: 10
149 | ```
150 |
151 | `return`后面不跟参数,只用于返回也是可以的。
152 |
153 | ```bash
154 | function name {
155 | commands
156 | return
157 | }
158 | ```
159 |
160 | ## 全局变量和局部变量,local 命令
161 |
162 | Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。这一点需要特别小心。
163 |
164 | ```bash
165 | # 脚本 test.sh
166 | fn () {
167 | foo=1
168 | echo "fn: foo = $foo"
169 | }
170 |
171 | fn
172 | echo "global: foo = $foo"
173 | ```
174 |
175 | 上面脚本的运行结果如下。
176 |
177 | ```bash
178 | $ bash test.sh
179 | fn: foo = 1
180 | global: foo = 1
181 | ```
182 |
183 | 上面例子中,变量`$foo`是在函数`fn`内部声明的,函数体外也可以读取。
184 |
185 | 函数体内不仅可以声明全局变量,还可以修改全局变量。
186 |
187 | ```bash
188 | #! /bin/bash
189 | foo=1
190 |
191 | fn () {
192 | foo=2
193 | }
194 |
195 | fn
196 |
197 | echo $foo
198 | ```
199 |
200 | 上面代码执行后,输出的变量`$foo`值为2。
201 |
202 | 函数里面可以用`local`命令声明局部变量。
203 |
204 | ```bash
205 | #! /bin/bash
206 | # 脚本 test.sh
207 | fn () {
208 | local foo
209 | foo=1
210 | echo "fn: foo = $foo"
211 | }
212 |
213 | fn
214 | echo "global: foo = $foo"
215 | ```
216 |
217 | 上面脚本的运行结果如下。
218 |
219 | ```bash
220 | $ bash test.sh
221 | fn: foo = 1
222 | global: foo =
223 | ```
224 |
225 | 上面例子中,`local`命令声明的`$foo`变量,只在函数体内有效,函数体外没有定义。
226 |
227 | ## 参考链接
228 |
229 | - [How to define and use functions in Linux Shell Script](https://www.linuxtechi.com/define-use-functions-linux-shell-script/), by Pradeep Kumar
230 |
231 |
--------------------------------------------------------------------------------
/docs/grammar.md:
--------------------------------------------------------------------------------
1 | # Bash 的基本语法
2 |
3 | 本章介绍 Bash 的最基本语法。
4 |
5 | ## echo 命令
6 |
7 | 由于后面的例子会大量用到`echo`命令,这里先介绍这个命令。
8 |
9 | `echo`命令的作用是在屏幕输出一行文本,可以将该命令的参数原样输出。
10 |
11 | ```bash
12 | $ echo hello world
13 | hello world
14 | ```
15 |
16 | 上面例子中,`echo`的参数是`hello world`,可以原样输出。
17 |
18 | 如果想要输出的是多行文本,即包括换行符。这时就需要把多行文本放在引号里面。
19 |
20 | ```bash
21 | $ echo "
22 |
23 | Page Title
24 |
25 |
26 | Page body.
27 |
28 | "
29 | ```
30 |
31 | 上面例子中,`echo`可以原样输出多行文本。
32 |
33 | ### `-n`参数
34 |
35 | 默认情况下,`echo`输出的文本末尾会有一个回车符。`-n`参数可以取消末尾的回车符,使得下一个提示符紧跟在输出内容的后面。
36 |
37 | ```bash
38 | $ echo -n hello world
39 | hello world$
40 | ```
41 |
42 | 上面例子中,`world`后面直接就是下一行的提示符`$`。
43 |
44 | ```bash
45 | $ echo a;echo b
46 | a
47 | b
48 |
49 | $ echo -n a;echo b
50 | ab
51 | ```
52 |
53 | 上面例子中,`-n`参数可以让两个`echo`命令的输出连在一起,出现在同一行。
54 |
55 | ### `-e`参数
56 |
57 | `-e`参数会解释引号(双引号和单引号)里面的特殊字符(比如换行符`\n`)。如果不使用`-e`参数,即默认情况下,引号会让特殊字符变成普通字符,`echo`不解释它们,原样输出。
58 |
59 | ```bash
60 | $ echo "Hello\nWorld"
61 | Hello\nWorld
62 |
63 | # 双引号的情况
64 | $ echo -e "Hello\nWorld"
65 | Hello
66 | World
67 |
68 | # 单引号的情况
69 | $ echo -e 'Hello\nWorld'
70 | Hello
71 | World
72 | ```
73 |
74 | 上面代码中,`-e`参数使得`\n`解释为换行符,导致输出内容里面出现换行。
75 |
76 | ## 命令格式
77 |
78 | 命令行环境中,主要通过使用 Shell 命令,进行各种操作。Shell 命令基本都是下面的格式。
79 |
80 | ```bash
81 | $ command [ arg1 ... [ argN ]]
82 | ```
83 |
84 | 上面代码中,`command`是具体的命令或者一个可执行文件,`arg1 ... argN`是传递给命令的参数,它们是可选的。
85 |
86 | ```bash
87 | $ ls -l
88 | ```
89 |
90 | 上面这个命令中,`ls`是命令,`-l`是参数。
91 |
92 | 有些参数是命令的配置项,这些配置项一般都以一个连词线开头,比如上面的`-l`。同一个配置项往往有长和短两种形式,比如`-l`是短形式,`--list`是长形式,它们的作用完全相同。短形式便于手动输入,长形式一般用在脚本之中,可读性更好,利于解释自身的含义。
93 |
94 | ```bash
95 | # 短形式
96 | $ ls -r
97 |
98 | # 长形式
99 | $ ls --reverse
100 | ```
101 |
102 | 上面命令中,`-r`是短形式,`--reverse`是长形式,作用完全一样。前者便于输入,后者便于理解。
103 |
104 | Bash 单个命令一般都是一行,用户按下回车键,就开始执行。有些命令比较长,写成多行会有利于阅读和编辑,这时可以在每一行的结尾加上反斜杠,Bash 就会将下一行跟当前行放在一起解释。
105 |
106 | ```bash
107 | $ echo foo bar
108 |
109 | # 等同于
110 | $ echo foo \
111 | bar
112 | ```
113 |
114 | ## 空格
115 |
116 | Bash 使用空格(或 Tab 键)区分不同的参数。
117 |
118 | ```bash
119 | $ command foo bar
120 | ```
121 |
122 | 上面命令中,`foo`和`bar`之间有一个空格,所以 Bash 认为它们是两个参数。
123 |
124 | 如果参数之间有多个空格,Bash 会自动忽略多余的空格。
125 |
126 | ```bash
127 | $ echo this is a test
128 | this is a test
129 | ```
130 |
131 | 上面命令中,`a`和`test`之间有多个空格,Bash 会忽略多余的空格。
132 |
133 | ## 分号
134 |
135 | 分号(`;`)是命令的结束符,使得一行可以放置多个命令,上一个命令执行结束后,再执行第二个命令。
136 |
137 | ```bash
138 | $ clear; ls
139 | ```
140 |
141 | 上面例子中,Bash 先执行`clear`命令,执行完成后,再执行`ls`命令。
142 |
143 | 注意,使用分号时,第二个命令总是接着第一个命令执行,不管第一个命令执行成功或失败。
144 |
145 | ## 命令的组合符`&&`和`||`
146 |
147 | 除了分号,Bash 还提供两个命令组合符`&&`和`||`,允许更好地控制多个命令之间的继发关系。
148 |
149 | ```bash
150 | Command1 && Command2
151 | ```
152 |
153 | 上面命令的意思是,如果`Command1`命令运行成功,则继续运行`Command2`命令。
154 |
155 | ```bash
156 | Command1 || Command2
157 | ```
158 |
159 | 上面命令的意思是,如果`Command1`命令运行失败,则继续运行`Command2`命令。
160 |
161 | 下面是一些例子。
162 |
163 | ```bash
164 | $ cat filelist.txt ; ls -l filelist.txt
165 | ```
166 |
167 | 上面例子中,只要`cat`命令执行结束,不管成功或失败,都会继续执行`ls`命令。
168 |
169 | ```bash
170 | $ cat filelist.txt && ls -l filelist.txt
171 | ```
172 |
173 | 上面例子中,只有`cat`命令执行成功,才会继续执行`ls`命令。如果`cat`执行失败(比如不存在文件`flielist.txt`),那么`ls`命令就不会执行。
174 |
175 | ```bash
176 | $ mkdir foo || mkdir bar
177 | ```
178 |
179 | 上面例子中,只有`mkdir foo`命令执行失败(比如`foo`目录已经存在),才会继续执行`mkdir bar`命令。如果`mkdir foo`命令执行成功,就不会创建`bar`目录了。
180 |
181 | ## type 命令
182 |
183 | Bash 本身内置了很多命令,同时也可以执行外部程序。怎么知道一个命令是内置命令,还是外部程序呢?
184 |
185 | `type`命令用来判断命令的来源。
186 |
187 | ```bash
188 | $ type echo
189 | echo is a shell builtin
190 | $ type ls
191 | ls is hashed (/bin/ls)
192 | ```
193 |
194 | 上面代码中,`type`命令告诉我们,`echo`是内部命令,`ls`是外部程序(`/bin/ls`)。
195 |
196 | `type`命令本身也是内置命令。
197 |
198 | ```bash
199 | $ type type
200 | type is a shell builtin
201 | ```
202 |
203 | 如果要查看一个命令的所有定义,可以使用`type`命令的`-a`参数。
204 |
205 | ```bash
206 | $ type -a echo
207 | echo is shell builtin
208 | echo is /usr/bin/echo
209 | echo is /bin/echo
210 | ```
211 |
212 | 上面代码表示,`echo`命令既是内置命令,也有对应的外部程序。
213 |
214 | `type`命令的`-t`参数,可以返回一个命令的类型:别名(alias),关键词(keyword),函数(function),内置命令(builtin)和文件(file)。
215 |
216 | ```bash
217 | $ type -t bash
218 | file
219 | $ type -t if
220 | keyword
221 | ```
222 |
223 | 上面例子中,`bash`是文件,`if`是关键词。
224 |
225 | ## 快捷键
226 |
227 | Bash 提供很多快捷键,可以大大方便操作。下面是一些最常用的快捷键,完整的介绍参见《行操作》一章。
228 |
229 | - `Ctrl + L`:清除屏幕并将当前行移到页面顶部。
230 | - `Ctrl + C`:中止当前正在执行的命令。
231 | - `Shift + PageUp`:向上滚动。
232 | - `Shift + PageDown`:向下滚动。
233 | - `Ctrl + U`:从光标位置删除到行首。
234 | - `Ctrl + K`:从光标位置删除到行尾。
235 | - `Ctrl + W`:删除光标位置前一个单词。
236 | - `Ctrl + D`:关闭 Shell 会话。
237 | - `↑`,`↓`:浏览已执行命令的历史记录。
238 |
239 | 除了上面的快捷键,Bash 还具有自动补全功能。命令输入到一半的时候,可以按下 Tab 键,Bash 会自动完成剩下的部分。比如,输入`tou`,然后按一下 Tab 键,Bash 会自动补上`ch`。
240 |
241 | 除了命令的自动补全,Bash 还支持路径的自动补全。有时,需要输入很长的路径,这时只需要输入前面的部分,然后按下 Tab 键,就会自动补全后面的部分。如果有多个可能的选择,按两次 Tab 键,Bash 会显示所有选项,让你选择。
242 |
243 |
--------------------------------------------------------------------------------
/docs/history.md:
--------------------------------------------------------------------------------
1 | # 操作历史
2 |
3 | ## 简介
4 |
5 | Bash 会保留用户的操作历史,即用户输入的每一条命令都会记录,默认是保存最近的500条命令。有了操作历史以后,就可以使用方向键的`↑`和`↓`,快速浏览上一条和下一条命令。
6 |
7 | 退出当前 Shell 的时候,Bash 会将用户在当前 Shell 的操作历史写入`~/.bash_history`文件,该文件默认储存500个操作。
8 |
9 | 环境变量`HISTFILE`总是指向这个文件。
10 |
11 | ```bash
12 | $ echo $HISTFILE
13 | /home/me/.bash_history
14 | ```
15 |
16 | ## history 命令
17 |
18 | `history`命令会输出`.bash_history`文件的全部内容,即输出操作历史。
19 |
20 | ```bash
21 | $ history
22 | ...
23 | 498 echo Goodbye
24 | 499 ls ~
25 | 500 cd
26 | ```
27 |
28 | 用户可以使用这个命令,查看最近的操作。相比直接读取`.bash_history`文件,它的优势在于所有命令之前加上了行号。最近的操作在最后面,行号最大。
29 |
30 | 如果想搜索某个以前执行的命令,可以配合`grep`命令搜索操作历史。
31 |
32 | ```bash
33 | $ history | grep /usr/bin
34 | ```
35 |
36 | 上面命令返回`.bash_history`文件里面,那些包含`/usr/bin`的命令。
37 |
38 | `history`命令的`-c`参数可以清除操作历史,即清空`.bash_history`文件。
39 |
40 | ```bash
41 | $ history -c
42 | ```
43 |
44 | ## 环境变量
45 |
46 | ### HISTTIMEFORMAT
47 |
48 | 通过定制环境变量`HISTTIMEFORMAT`,`history`的输出结果还可以显示每个操作的时间。
49 |
50 | ```bash
51 | $ export HISTTIMEFORMAT='%F %T '
52 | $ history
53 | 1 2013-06-09 10:40:12 cat /etc/issue
54 | 2 2013-06-09 10:40:12 clear
55 | ```
56 |
57 | 上面代码中,`%F`相当于`%Y - %m - %d`(年-月-日),`%T`相当于` %H : %M : %S`(时:分:秒)。
58 |
59 | 只要设置`HISTTIMEFORMAT`这个环境变量,就会在`.bash_history`文件保存命令的执行时间戳。如果不设置,就不会保存时间戳。
60 |
61 | ### HISTSIZE
62 |
63 | 环境变量`HISTSIZE`设置保存历史操作的数量。
64 |
65 | ```bash
66 | $ export HISTSIZE=10000
67 | ```
68 |
69 | 上面命令设置保存过去10000条操作历史。
70 |
71 | 如果不希望保存本次操作的历史,可以设置`HISTSIZE`等于0。
72 |
73 | ```bash
74 | export HISTSIZE=0
75 | ```
76 |
77 | 如果`HISTSIZE=0`写入用户主目录的`~/.bashrc`文件,那么就不会保留该用户的操作历史。如果写入`/etc/profile`,整个系统都不会保留操作历史。
78 |
79 | ### HISTIGNORE
80 |
81 | 环境变量`HISTIGNORE`可以设置哪些命令不写入操作历史。
82 |
83 | ```bash
84 | export HISTIGNORE='pwd:ls:exit'
85 | ```
86 |
87 | 上面示例设置,`pwd`、`ls`、`exit`这三个命令不写入操作历史。
88 |
89 | ## Ctrl + r
90 |
91 | 输入命令时,按下`Ctrl + r`快捷键,就可以搜索操作历史,选择以前执行过的命令。
92 |
93 | `Ctrl + r`相当于打开一个`.bash_history`文件的搜索接口,直接键入命令的开头部分,Shell 就会自动在该文件中反向查询(即先查询最近的命令),显示最近一条匹配的结果,这时按下回车键,就会执行那条命令。
94 |
95 | ## ! 命令
96 |
97 | ### ! + 行号
98 |
99 | 操作历史的每一条记录都有行号。知道了命令的行号以后,可以用`感叹号 + 行号`执行该命令。如果想要执行`.bash_history`里面的第8条命令,可以像下面这样操作。
100 |
101 | ```bash
102 | $ !8
103 | ```
104 |
105 | ### !- 数字
106 |
107 | 如果想执行本次 Shell 对话中倒数的命令,比如执行倒数第3条命令,就可以输入`!-3`。
108 |
109 | ```bash
110 | $ touch a.txt
111 | $ touch b.txt
112 | $ touch c.txt
113 |
114 | $ !-3
115 | touch a.txt
116 | ```
117 |
118 | 上面示例中,`!-3`返回倒数第3条命令,即`touch a.txt`。
119 |
120 | 它跟`! + 行号`的主要区别是,后者是在`.bash_history`文件中从头开始计算行数,而`!- 数字`是从底部开始向上计算行数。
121 |
122 | ### !!
123 |
124 | `!!`命令返回上一条命令。如果需要重复执行某一条命令,就可以不断键入`!!`,这样非常方便。它等同于`!-1`。
125 |
126 | ```bash
127 | $ echo hello
128 | hello
129 |
130 | $ !!
131 | echo hello
132 | hello
133 | ```
134 |
135 | 上面示例中,`!!`会返回并执行上一条命令`echo hello`。
136 |
137 | 有时候,我们使用某条命令,系统报错没有权限,这时就可以使用`sudo !!`。
138 |
139 | ```bash
140 | # 报错,没有执行权限
141 | $ yum update
142 |
143 | $ sudo !!
144 | sudo yum update
145 | ```
146 |
147 | 上面示例中,`sudo !!`返回`sudo yum update`,从而就可以正确执行了。
148 |
149 | ### ! + 搜索词
150 |
151 | `感叹号 + 搜索词`可以快速执行匹配的命令。
152 |
153 | ```bash
154 | $ echo Hello World
155 | Hello World
156 |
157 | $ echo Goodbye
158 | Goodbye
159 |
160 | $ !e
161 | echo Goodbye
162 | Goodbye
163 | ```
164 |
165 | 上面例子中,`!e`表示找出操作历史之中,最近的那一条以`e`开头的命令并执行。Bash 会先输出那一条命令`echo Goodbye`,然后直接执行。
166 |
167 | 同理,`!echo`也会执行最近一条以`echo`开头的命令。
168 |
169 | ```bash
170 | $ !echo
171 | echo Goodbye
172 | Goodbye
173 |
174 | $ !echo H
175 | echo Goodbye H
176 | Goodbye H
177 |
178 | $ !echo H G
179 | echo Goodbye H G
180 | Goodbye H G
181 | ```
182 |
183 | 注意,`感叹号 + 搜索词`语法只会匹配命令,不会匹配参数。所以`!echo H`不会执行`echo Hello World`,而是会执行`echo Goodbye`,并把参数`H`附加在这条命令之后。同理,`!echo H G`也是等同于`echo Goodbye`命令之后附加`H G`。
184 |
185 | 由于`感叹号 + 搜索词`会扩展成以前执行过的命令,所以含有`!`的字符串放在双引号里面,必须非常小心,如果它后面有非空格的字符,就很有可能报错。
186 |
187 | ```bash
188 | $ echo "I say:\"hello!\""
189 | bash: !\: event not found
190 | ```
191 |
192 | 上面的命令会报错,原因是感叹号后面是一个反斜杠,Bash 会尝试寻找,以前是否执行过反斜杠开头的命令,一旦找不到就会报错。解决方法就是在感叹号前面,也加上反斜杠。
193 |
194 | ```bash
195 | $ echo "I say:\"hello\!\""
196 | I say:"hello\!"
197 | ```
198 |
199 | ### !? + 搜索词
200 |
201 | `!? + 搜索词`可以搜索命令的任意部分,包括参数部分。它跟`! + 搜索词`的主要区别是,后者是从行首开始匹配。
202 |
203 | ```bash
204 | $ cat hello.txt
205 | Hello world ..!
206 |
207 | $ !?hello.txt
208 | cat hello.txt
209 | Hello world ..!
210 | ```
211 |
212 | 上面示例中,`!?hello.txt`会返回最近一条包括`hello.txt`的命令。
213 |
214 | ### !$,!*
215 |
216 | `!$`代表上一个命令的最后一个参数,它的另一种写法是`$_`。
217 |
218 | `!*`代表上一个命令的所有参数,即除了命令以外的所有部分。
219 |
220 | ```bash
221 | $ cp a.txt b.txt
222 | $ echo !$
223 | b.txt
224 |
225 | $ cp a.txt b.txt
226 | $ echo !*
227 | a.txt b.txt
228 | ```
229 |
230 | 上面示例中,`!$`代表上一个命令的最后一个参数(`b.txt`),`!*`代表上一个命令的所有参数(`a.txt b.txt`)。
231 |
232 | 如果想匹配上一个命令的某个指定位置的参数,使用`!:n`。
233 |
234 | ```bash
235 | $ ls a.txt b.txt c.txt
236 |
237 | $ echo !:2
238 | b.txt
239 | ```
240 |
241 | 上面示例中,`!:2`返回上一条命令的第二个参数(`b.txt`)。
242 |
243 | 这种写法的`!:$`,代表上一个命令的最后一个参数。事实上,`!$`就是`!:$`的简写形式。
244 |
245 | ```bash
246 | $ ls a.txt b.txt c.txt
247 |
248 | $ echo !:$
249 | echo c.txt
250 | c.txt
251 | ```
252 |
253 | 上面示例中,`!:$`代表上一条命令的最后一个参数(`c.txt`)。
254 |
255 | 如果想匹配更久以前的命令的参数,可以使用`!<命令>:n`(指定位置的参数)和`!<命令>:$`(最后一个参数)。
256 |
257 | ```bash
258 | $ ls !mkdir:$
259 | ```
260 |
261 | 上面示例中,`!mkdir:$`会返回前面最后一条`mkdir`命令的最后一个参数。
262 |
263 | ```bash
264 | $ ls !mk:2
265 | ```
266 |
267 | 上面示例中,`!mk:2`会返回前面最后一条以`mk`开头的命令的第二个参数。
268 |
269 | ### !:p
270 |
271 | 如果只是想输出上一条命令,而不是执行它,可以使用`!:p`。
272 |
273 | ```bash
274 | $ echo hello
275 |
276 | $ !:p
277 | echo hello
278 | ```
279 |
280 | 上面示例中,`!:p`只会输出`echo hello`,而不会执行这条命令。
281 |
282 | 如果想输出最近一条匹配的命令,而不执行它,可以使用`!<命令>:p`。
283 |
284 | ```bash
285 | $ !su:p
286 | ```
287 |
288 | 上面示例中,`!su:p`会输出前面最近一条以`su`开头的命令,而不执行它。
289 |
290 | ## `^string1^string2`
291 |
292 | `^string1^string2`用来执行最近一条包含`string1`的命令,将其替换成`string2`。
293 |
294 | ```bash
295 | $ rm /var/log/httpd/error.log
296 | $ ^error^access
297 | rm /var/log/httpd/access.log
298 | ```
299 |
300 | 上面示例中,`^error^access`将最近一条含有`error`的命令里面的`error`,替换成`access`。
301 |
302 | ## histverify 参数
303 |
304 | 上面的那些快捷命令(比如`!!`命令),都是找到匹配的命令后,直接执行。如果希望增加一个确认步骤,先输出是什么命令,让用户确认后再执行,可以打开 Shell 的`histverify`选项。
305 |
306 | ```bash
307 | $ shopt -s histverify
308 | ```
309 |
310 | 打开`histverify`这个选项后,使用`!`快捷键所返回的命令,就会先输出,等到用户按下回车键后再执行。
311 |
312 | ## 快捷键
313 |
314 | 下面是其他一些与操作历史相关的快捷键。
315 |
316 | - `Ctrl + p`:显示上一个命令,与向上箭头效果相同(previous)。
317 | - `Ctrl + n`:显示下一个命令,与向下箭头效果相同(next)。
318 | - `Alt + <`:显示第一个命令。
319 | - `Alt + >`:显示最后一个命令,即当前的命令。
320 | - `Ctrl + o`:执行历史文件里面的当前条目,并自动显示下一条命令。这对重复执行某个序列的命令很有帮助。
321 |
322 | ## 参考链接
323 |
324 | - [Bash bang commands: A must-know trick for the Linux command line](https://www.redhat.com/sysadmin/bash-bang-commands)
325 |
326 |
--------------------------------------------------------------------------------
/docs/intro.md:
--------------------------------------------------------------------------------
1 | # Bash 简介
2 |
3 | Bash 是 Unix 系统和 Linux 系统的一种 Shell(命令行环境),是目前绝大多数 Linux 发行版的默认 Shell。
4 |
5 | ## Shell 的含义
6 |
7 | 学习 Bash,首先需要理解 Shell 是什么。Shell 这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。
8 |
9 | 具体来说,Shell 这个词有多种含义。
10 |
11 | 首先,Shell 是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为 CLI)。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。本书中,除非特别指明,Shell 指的就是命令行环境。
12 |
13 | 其次,Shell 是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本(script)。这些脚本都通过 Shell 的解释执行,而不通过编译。
14 |
15 | 最后,Shell 是一个工具箱,提供了各种小工具,供用户方便地使用操作系统的功能。
16 |
17 | ## Shell 的种类
18 |
19 | Shell 有很多种,只要能给用户提供命令行环境的程序,都可以看作是 Shell。
20 |
21 | 历史上,主要的 Shell 有下面这些。
22 |
23 | - Bourne Shell(sh)
24 | - Bourne Again shell(bash)
25 | - C Shell(csh)
26 | - TENEX C Shell(tcsh)
27 | - Korn shell(ksh)
28 | - Z Shell(zsh)
29 | - Friendly Interactive Shell(fish)
30 |
31 | Bash 是目前最常用的 Shell,除非特别指明,下文的 Shell 和 Bash 当作同义词使用,可以互换。
32 |
33 | 下面的命令可以查看当前设备的默认 Shell。
34 |
35 | ```bash
36 | $ echo $SHELL
37 | /bin/bash
38 | ```
39 |
40 | 当前正在使用的 Shell 不一定是默认 Shell,一般来说,`ps`命令结果的倒数第二行是当前 Shell。
41 |
42 | ```bash
43 | $ ps
44 | PID TTY TIME CMD
45 | 4467 pts/0 00:00:00 bash
46 | 5379 pts/0 00:00:00 ps
47 | ```
48 |
49 | 上面示例中,`ps`命令结果的倒数第二行显示,运行的命令(`cmd`)是`bash`,表明当前正在使用的 Shell 是 Bash。
50 |
51 | 下面的命令可以查看当前的 Linux 系统安装的所有 Shell。
52 |
53 | ```bash
54 | $ cat /etc/shells
55 | ```
56 |
57 | 上面三个命令中,`$`是命令行环境的提示符,用户只需要输入提示符后面的内容。
58 |
59 | Linux 允许每个用户使用不同的 Shell,用户的默认 Shell 一般都是 Bash,或者与 Bash 兼容。
60 |
61 | 使用`chsh`命令,可以改变系统的默认 Shell。举例来说,要将默认 Shell 从 Bash 改成 Fish,首先要找出 Fish 可执行文件的位置。
62 |
63 | ```bash
64 | $ which fish
65 | ```
66 |
67 | 上面命令找出 Fish 可执行文件的位置,一般是`/usr/bin/fish`。
68 |
69 | 然后,使用`chsh`命令切换默认 Shell。
70 |
71 | ```bash
72 | $ chsh -s /usr/bin/fish
73 | ```
74 |
75 | 上面命令会将当前的默认 Shell 改成 Fish。
76 |
77 | ## 命令行环境
78 |
79 | ### 终端模拟器
80 |
81 | 如果是不带有图形环境的 Linux 系统(比如专用于服务器的系统),启动后就直接是命令行环境。
82 |
83 | 不过,现在大部分的 Linux 发行版,尤其是针对普通用户的发行版,都是图形环境。用户登录系统后,自动进入图形环境,需要自己启动终端模拟器,才能进入命令行环境。
84 |
85 | 所谓“终端模拟器”(terminal emulator)就是一个模拟命令行窗口的程序,让用户在一个窗口中使用命令行环境,并且提供各种附加功能,比如调整颜色、字体大小、行距等等。
86 |
87 | 不同 Linux 发行版(准确地说是不同的桌面环境)带有的终端程序是不一样的,比如 KDE 桌面环境的终端程序是 konsole,Gnome 桌面环境的终端程序是 gnome-terminal,用户也可以安装第三方的终端程序。所有终端程序,尽管名字不同,基本功能都是一样的,就是让用户可以进入命令行环境,使用 Shell。
88 |
89 | ### 命令行提示符
90 |
91 | 进入命令行环境以后,用户会看到 Shell 的提示符。提示符往往是一串前缀,最后以一个美元符号`$`结尾,用户可以在这个符号后面输入各种命令。
92 |
93 | ```bash
94 | [user@hostname] $
95 | ```
96 |
97 | 上面例子中,完整的提示符是`[user@hostname] $`,其中前缀是用户名(`user`)加上`@`,再加主机名(`hostname`)。比如,用户名是`bill`,主机名是`home-machine`,前缀就是`bill@home-machine`。
98 |
99 | 注意,根用户(root)的提示符,不以美元符号(`$`)结尾,而以井号(`#`)结尾,用来提醒用户,现在具有根权限,可以执行各种操作,务必小心,不要出现误操作。这个符号是可以自己定义的,详见《命令提示符》一章。
100 |
101 | 为了简洁,后文的命令行提示符都只使用`$`表示。
102 |
103 | ### 进入和退出方法
104 |
105 | 进入命令行环境以后,一般就已经打开 Bash 了。如果你的 Shell 不是 Bash,可以输入`bash`命令启动 Bash。
106 |
107 | ```bash
108 | $ bash
109 | ```
110 |
111 | 退出 Bash 环境,可以使用`exit`命令,也可以同时按下`Ctrl + d`。
112 |
113 | ```bash
114 | $ exit
115 | ```
116 |
117 | Bash 的基本用法就是在命令行输入各种命令,非常直观。作为练习,可以试着输入`pwd`命令。按下回车键,就会显示当前所在的目录。
118 |
119 | ```bash
120 | $ pwd
121 | /home/me
122 | ```
123 |
124 | 如果不小心输入了`pwe`,会返回一个提示,表示输入出错,没有对应的可执行程序。
125 |
126 | ```bash
127 | $ pwe
128 | bash: pwe:未找到命令
129 | ```
130 |
131 | ## Shell 和 Bash 的历史
132 |
133 | Shell 伴随着 Unix 系统的诞生而诞生。
134 |
135 | 1969年,Ken Thompson 和 Dennis Ritchie 开发了第一版的 Unix。
136 |
137 | 1971年,Ken Thompson 编写了最初的 Shell,称为 Thompson shell,程序名是`sh`,方便用户使用 Unix。
138 |
139 | 1973年至1975年间,John R. Mashey 扩展了最初的 Thompson shell,添加了编程功能,使得 Shell 成为一种编程语言。这个版本的 Shell 称为 Mashey shell。
140 |
141 | 1976年,Stephen Bourne 结合 Mashey shell 的功能,重写一个新的 Shell,称为 Bourne shell。
142 |
143 | 1978年,加州大学伯克利分校的 Bill Joy 开发了 C shell,为 Shell 提供 C 语言的语法,程序名是`csh`。它是第一个真正替代`sh`的 UNIX shell,被合并到 Berkeley UNIX 的 2BSD 版本中。
144 |
145 | 1979年,UNIX 第七版发布,内置了 Bourne Shell,导致它成为 Unix 的默认 Shell。注意,Thompson shell、Mashey shell 和 Bourne shell 都是贝尔实验室的产品,程序名都是`sh`。对于用户来说,它们是同一个东西,只是底层代码不同而已。
146 |
147 | 1983年,David Korn 开发了Korn shell,程序名是`ksh`。
148 |
149 | 1985年,Richard Stallman 成立了自由软件基金会(FSF),由于 Shell 的版权属于贝尔公司,所以他决定写一个自由版权的、使用 GNU 许可证的 Shell 程序,避免 Unix 的版权争议。
150 |
151 | 1988年,自由软件基金会的第一个付薪程序员 Brian Fox 写了一个 Shell,功能基本上是 Bourne shell 的克隆,叫做 Bourne-Again SHell,简称 Bash,程序名为`bash`,任何人都可以免费使用。后来,它逐渐成为 Linux 系统的标准 Shell。
152 |
153 | 1989年,Bash 发布1.0版。
154 |
155 | 1996年,Bash 发布2.0版。
156 |
157 | 2004年,Bash 发布3.0版。
158 |
159 | 2009年,Bash 发布4.0版。
160 |
161 | 2019年,Bash 发布5.0版。
162 |
163 | 用户可以通过`bash`命令的`--version`参数或者环境变量`$BASH_VERSION`,查看本机的 Bash 版本。
164 |
165 | ```bash
166 | $ bash --version
167 | GNU bash,版本 5.0.3(1)-release (x86_64-pc-linux-gnu)
168 |
169 | # 或者
170 | $ echo $BASH_VERSION
171 | 5.0.3(1)-release
172 | ```
173 |
174 |
--------------------------------------------------------------------------------
/docs/loop.md:
--------------------------------------------------------------------------------
1 | # 循环
2 |
3 | Bash 提供三种循环语法`for`、`while`和`until`。
4 |
5 | ## while 循环
6 |
7 | `while`循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。
8 |
9 | ```bash
10 | while condition; do
11 | commands
12 | done
13 | ```
14 |
15 | 上面代码中,只要满足条件`condition`,就会执行命令`commands`。然后,再次判断是否满足条件`condition`,只要满足,就会一直执行下去。只有不满足条件,才会退出循环。
16 |
17 | 循环条件`condition`可以使用`test`命令,跟`if`结构的判断条件写法一致。
18 |
19 | ```bash
20 | #!/bin/bash
21 |
22 | number=0
23 | while [ "$number" -lt 10 ]; do
24 | echo "Number = $number"
25 | number=$((number + 1))
26 | done
27 | ```
28 |
29 | 上面例子中,只要变量`$number`小于10,就会不断加1,直到`$number`等于10,然后退出循环。
30 |
31 | 关键字`do`可以跟`while`不在同一行,这时两者之间不需要使用分号分隔。
32 |
33 | ```bash
34 | while true
35 | do
36 | echo 'Hi, while looping ...';
37 | done
38 | ```
39 |
40 | 上面的例子会无限循环,可以按下 Ctrl + c 停止。
41 |
42 | `while`循环写成一行,也是可以的。
43 |
44 | ```bash
45 | $ while true; do echo 'Hi, while looping ...'; done
46 | ```
47 |
48 | `while`的条件部分也可以是执行一个命令。
49 |
50 | ```bash
51 | $ while echo 'ECHO'; do echo 'Hi, while looping ...'; done
52 | ```
53 |
54 | 上面例子中,判断条件是`echo 'ECHO'`。由于这个命令总是执行成功,所以上面命令会产生无限循环。
55 |
56 | `while`的条件部分可以执行任意数量的命令,但是执行结果的真伪只看最后一个命令的执行结果。
57 |
58 | ```bash
59 | $ while true; false; do echo 'Hi, looping ...'; done
60 | ```
61 |
62 | 上面代码运行后,不会有任何输出,因为`while`的最后一个命令是`false`。
63 |
64 | ## until 循环
65 |
66 | `until`循环与`while`循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。
67 |
68 | ```bash
69 | until condition; do
70 | commands
71 | done
72 | ```
73 |
74 | 关键字`do`可以与`until`不写在同一行,这时两者之间不需要分号分隔。
75 |
76 | ```bash
77 | until condition
78 | do
79 | commands
80 | done
81 | ```
82 |
83 | 下面是一个例子。
84 |
85 | ```bash
86 | $ until false; do echo 'Hi, until looping ...'; done
87 | Hi, until looping ...
88 | Hi, until looping ...
89 | Hi, until looping ...
90 | ^C
91 | ```
92 |
93 | 上面代码中,`until`的部分一直为`false`,导致命令无限运行,必须按下 Ctrl + c 终止。
94 |
95 | ```bash
96 | #!/bin/bash
97 |
98 | number=0
99 | until [ "$number" -ge 10 ]; do
100 | echo "Number = $number"
101 | number=$((number + 1))
102 | done
103 | ```
104 |
105 | 上面例子中,只要变量`number`小于10,就会不断加1,直到`number`大于等于10,就退出循环。
106 |
107 | `until`的条件部分也可以是一个命令,表示在这个命令执行成功之前,不断重复尝试。
108 |
109 | ```bash
110 | until cp $1 $2; do
111 | echo 'Attempt to copy failed. waiting...'
112 | sleep 5
113 | done
114 | ```
115 |
116 | 上面例子表示,只要`cp $1 $2`这个命令执行不成功,就5秒钟后再尝试一次,直到成功为止。
117 |
118 | `until`循环都可以转为`while`循环,只要把条件设为否定即可。上面这个例子可以改写如下。
119 |
120 | ```bash
121 | while ! cp $1 $2; do
122 | echo 'Attempt to copy failed. waiting...'
123 | sleep 5
124 | done
125 | ```
126 |
127 | 一般来说,`until`用得比较少,完全可以统一都使用`while`。
128 |
129 | ## for...in 循环
130 |
131 | `for...in`循环用于遍历列表的每一项。
132 |
133 | ```bash
134 | for variable in list
135 | do
136 | commands
137 | done
138 | ```
139 |
140 | 上面语法中,`for`循环会依次从`list`列表中取出一项,作为变量`variable`,然后在循环体中进行处理。
141 |
142 | 关键词`do`可以跟`for`写在同一行,两者使用分号分隔。
143 |
144 | ```bash
145 | for variable in list; do
146 | commands
147 | done
148 | ```
149 |
150 | 下面是一个例子。
151 |
152 | ```bash
153 | #!/bin/bash
154 |
155 | for i in word1 word2 word3; do
156 | echo $i
157 | done
158 | ```
159 |
160 | 上面例子中,`word1 word2 word3`是一个包含三个单词的列表,变量`i`依次等于`word1`、`word2`、`word3`,命令`echo $i`则会相应地执行三次。
161 |
162 | 列表可以由通配符产生。
163 |
164 | ```bash
165 | for i in *.png; do
166 | ls -l $i
167 | done
168 | ```
169 |
170 | 上面例子中,`*.png`会替换成当前目录中所有 PNG 图片文件,变量`i`会依次等于每一个文件。
171 |
172 | 列表也可以通过子命令产生。
173 |
174 | ```bash
175 | #!/bin/bash
176 |
177 | count=0
178 | for i in $(cat ~/.bash_profile); do
179 | count=$((count + 1))
180 | echo "Word $count ($i) contains $(echo -n $i | wc -c) characters"
181 | done
182 | ```
183 |
184 | 上面例子中,`cat ~/.bash_profile`命令会输出`~/.bash_profile`文件的内容,然后通过遍历每一个词,计算该文件一共包含多少个词,以及每个词有多少个字符。
185 |
186 | `in list`的部分可以省略,这时`list`默认等于脚本的所有参数`$@`。但是,为了可读性,最好还是不要省略,参考下面的例子。
187 |
188 | ```bash
189 | for filename; do
190 | echo "$filename"
191 | done
192 |
193 | # 等同于
194 |
195 | for filename in "$@" ; do
196 | echo "$filename"
197 | done
198 | ```
199 |
200 | 在函数体中也是一样的,`for...in`循环省略`in list`的部分,则`list`默认等于函数的所有参数。
201 |
202 | ## for 循环
203 |
204 | `for`循环还支持 C 语言的循环语法。
205 |
206 | ```bash
207 | for (( expression1; expression2; expression3 )); do
208 | commands
209 | done
210 | ```
211 |
212 | 上面代码中,`expression1`用来初始化循环条件,`expression2`用来决定循环结束的条件,`expression3`在每次循环迭代的末尾执行,用于更新值。
213 |
214 | 注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号`$`。
215 |
216 | 它等同于下面的`while`循环。
217 |
218 | ```bash
219 | (( expression1 ))
220 | while (( expression2 )); do
221 | commands
222 | (( expression3 ))
223 | done
224 | ```
225 |
226 | 下面是一个例子。
227 |
228 | ```bash
229 | for (( i=0; i<5; i=i+1 )); do
230 | echo $i
231 | done
232 | ```
233 |
234 | 上面代码中,初始化变量`i`的值为0,循环执行的条件是`i`小于5。每次循环迭代结束时,`i`的值加1。
235 |
236 | `for`条件部分的三个语句,都可以省略。
237 |
238 | ```bash
239 | for ((;;))
240 | do
241 | read var
242 | if [ "$var" = "." ]; then
243 | break
244 | fi
245 | done
246 | ```
247 |
248 | 上面脚本会反复读取命令行输入,直到用户输入了一个点(`.`)为止,才会跳出循环。
249 |
250 | ## break,continue
251 |
252 | Bash 提供了两个内部命令`break`和`continue`,用来在循环内部跳出循环。
253 |
254 | `break`命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。
255 |
256 | ```bash
257 | #!/bin/bash
258 |
259 | for number in 1 2 3 4 5 6
260 | do
261 | echo "number is $number"
262 | if [ "$number" = "3" ]; then
263 | break
264 | fi
265 | done
266 | ```
267 |
268 | 上面例子只会打印3行结果。一旦变量`$number`等于3,就会跳出循环,不再继续执行。
269 |
270 | `continue`命令立即终止本轮循环,开始执行下一轮循环。
271 |
272 | ```bash
273 | #!/bin/bash
274 |
275 | while read -p "What file do you want to test?" filename
276 | do
277 | if [ ! -e "$filename" ]; then
278 | echo "The file does not exist."
279 | continue
280 | fi
281 |
282 | echo "You entered a valid file.."
283 | done
284 | ```
285 |
286 | 上面例子中,只要用户输入的文件不存在,`continue`命令就会生效,直接进入下一轮循环(让用户重新输入文件名),不再执行后面的打印语句。
287 |
288 | ## select 结构
289 |
290 | `select`结构主要用来生成简单的菜单。它的语法与`for...in`循环基本一致。
291 |
292 | ```bash
293 | select name
294 | [in list]
295 | do
296 | commands
297 | done
298 | ```
299 |
300 | Bash 会对`select`依次进行下面的处理。
301 |
302 | 1. `select`生成一个菜单,内容是列表`list`的每一项,并且每一项前面还有一个数字编号。
303 | 1. Bash 提示用户选择一项,输入它的编号。
304 | 1. 用户输入以后,Bash 会将该项的内容存在变量`name`,该项的编号存入环境变量`REPLY`。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。
305 | 1. 执行命令体`commands`。
306 | 1. 执行结束后,回到第一步,重复这个过程。
307 |
308 | 下面是一个例子。
309 |
310 | ```bash
311 | #!/bin/bash
312 | # select.sh
313 |
314 | select brand in Samsung Sony iphone symphony Walton
315 | do
316 | echo "You have chosen $brand"
317 | done
318 | ```
319 |
320 | 执行上面的脚本,Bash 会输出一个品牌的列表,让用户选择。
321 |
322 | ```bash
323 | $ ./select.sh
324 | 1) Samsung
325 | 2) Sony
326 | 3) iphone
327 | 4) symphony
328 | 5) Walton
329 | #?
330 | ```
331 |
332 | 如果用户没有输入编号,直接按回车键。Bash 就会重新输出一遍这个菜单,直到用户按下`Ctrl + c`,退出执行。
333 |
334 | `select`可以与`case`结合,针对不同项,执行不同的命令。
335 |
336 | ```bash
337 | #!/bin/bash
338 |
339 | echo "Which Operating System do you like?"
340 |
341 | select os in Ubuntu LinuxMint Windows8 Windows10 WindowsXP
342 | do
343 | case $os in
344 | "Ubuntu"|"LinuxMint")
345 | echo "I also use $os."
346 | ;;
347 | "Windows8" | "Windows10" | "WindowsXP")
348 | echo "Why don't you try Linux?"
349 | ;;
350 | *)
351 | echo "Invalid entry."
352 | break
353 | ;;
354 | esac
355 | done
356 | ```
357 |
358 | 上面例子中,`case`针对用户选择的不同项,执行不同的命令。
359 |
360 | ## 参考链接
361 |
362 | - [Bash Select Command](https://linuxhint.com/bash_select_command/), Fahmida Yesmin
363 |
364 |
--------------------------------------------------------------------------------
/docs/mktemp.md:
--------------------------------------------------------------------------------
1 | # mktemp 命令,trap 命令
2 |
3 | Bash 脚本有时需要创建临时文件或临时目录。常见的做法是,在`/tmp`目录里面创建文件或目录,这样做有很多弊端,使用`mktemp`命令是最安全的做法。
4 |
5 | ## 临时文件的安全问题
6 |
7 | 直接创建临时文件,尤其在`/tmp`目录里面,往往会导致安全问题。
8 |
9 | 首先,`/tmp`目录是所有人可读写的,任何用户都可以往该目录里面写文件。创建的临时文件也是所有人可读的。
10 |
11 | ```bash
12 | $ touch /tmp/info.txt
13 | $ ls -l /tmp/info.txt
14 | -rw-r--r-- 1 ruanyf ruanyf 0 12月 28 17:12 /tmp/info.txt
15 | ```
16 |
17 | 上面命令在`/tmp`目录直接创建文件,该文件默认是所有人可读的。
18 |
19 | 其次,如果攻击者知道临时文件的文件名,他可以创建符号链接,链接到临时文件,可能导致系统运行异常。攻击者也可能向脚本提供一些恶意数据。因此,临时文件最好使用不可预测、每次都不一样的文件名,防止被利用。
20 |
21 | 最后,临时文件使用完毕,应该删除。但是,脚本意外退出时,往往会忽略清理临时文件。
22 |
23 | 生成临时文件应该遵循下面的规则。
24 |
25 | > - 创建前检查文件是否已经存在。
26 | > - 确保临时文件已成功创建。
27 | > - 临时文件必须有权限的限制。
28 | > - 临时文件要使用不可预测的文件名。
29 | > - 脚本退出时,要删除临时文件(使用`trap`命令)。
30 |
31 | ## mktemp 命令的用法
32 |
33 | `mktemp`命令就是为安全创建临时文件而设计的。虽然在创建临时文件之前,它不会检查临时文件是否存在,但是它支持唯一文件名和清除机制,因此可以减轻安全攻击的风险。
34 |
35 | 直接运行`mktemp`命令,就能生成一个临时文件。
36 |
37 | ```bash
38 | $ mktemp
39 | /tmp/tmp.4GcsWSG4vj
40 |
41 | $ ls -l /tmp/tmp.4GcsWSG4vj
42 | -rw------- 1 ruanyf ruanyf 0 12月 28 12:49 /tmp/tmp.4GcsWSG4vj
43 | ```
44 |
45 | 上面命令中,`mktemp`命令生成的临时文件名是随机的,而且权限是只有用户本人可读写。
46 |
47 | Bash 脚本使用`mktemp`命令的用法如下。
48 |
49 | ```bash
50 | #!/bin/bash
51 |
52 | TMPFILE=$(mktemp)
53 | echo "Our temp file is $TMPFILE"
54 | ```
55 |
56 | 为了确保临时文件创建成功,`mktemp`命令后面最好使用 OR 运算符(`||`),保证创建失败时退出脚本。
57 |
58 | ```bash
59 | #!/bin/bash
60 |
61 | TMPFILE=$(mktemp) || exit 1
62 | echo "Our temp file is $TMPFILE"
63 | ```
64 |
65 | 为了保证脚本退出时临时文件被删除,可以使用`trap`命令指定退出时的清除操作。
66 |
67 | ```bash
68 | #!/bin/bash
69 |
70 | trap 'rm -f "$TMPFILE"' EXIT
71 |
72 | TMPFILE=$(mktemp) || exit 1
73 | echo "Our temp file is $TMPFILE"
74 | ```
75 |
76 | ## mktemp 命令的参数
77 |
78 | `-d`参数可以创建一个临时目录。
79 |
80 | ```bash
81 | $ mktemp -d
82 | /tmp/tmp.Wcau5UjmN6
83 | ```
84 |
85 | `-p`参数可以指定临时文件所在的目录。默认是使用`$TMPDIR`环境变量指定的目录,如果这个变量没设置,那么使用`/tmp`目录。
86 |
87 | ```bash
88 | $ mktemp -p /home/ruanyf/
89 | /home/ruanyf/tmp.FOKEtvs2H3
90 | ```
91 |
92 | `-t`参数可以指定临时文件的文件名模板,模板的末尾必须至少包含三个连续的`X`字符,表示随机字符,建议至少使用六个`X`。默认的文件名模板是`tmp.`后接十个随机字符。
93 |
94 | ```bash
95 | $ mktemp -t mytemp.XXXXXXX
96 | /tmp/mytemp.yZ1HgZV
97 | ```
98 |
99 | ## trap 命令
100 |
101 | `trap`命令用来在 Bash 脚本中响应系统信号。
102 |
103 | 最常见的系统信号就是 SIGINT(中断),即按 Ctrl + C 所产生的信号。`trap`命令的`-l`参数,可以列出所有的系统信号。
104 |
105 | ```bash
106 | $ trap -l
107 | 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
108 | 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
109 | 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
110 | 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
111 | 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
112 | 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
113 | 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
114 | 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
115 | 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
116 | 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
117 | 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
118 | 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
119 | 63) SIGRTMAX-1 64) SIGRTMAX
120 | ```
121 |
122 | `trap`的命令格式如下。
123 |
124 | ```bash
125 | $ trap [动作] [信号1] [信号2] ...
126 | ```
127 |
128 | 上面代码中,“动作”是一个 Bash 命令,“信号”常用的有以下几个。
129 |
130 | > - HUP:编号1,脚本与所在的终端脱离联系。
131 | > - INT:编号2,用户按下 Ctrl + C,意图让脚本终止运行。
132 | > - QUIT:编号3,用户按下 Ctrl + 斜杠,意图退出脚本。
133 | > - KILL:编号9,该信号用于杀死进程。
134 | > - TERM:编号15,这是`kill`命令发出的默认信号。
135 | > - EXIT:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。
136 |
137 | `trap`命令响应`EXIT`信号的写法如下。
138 |
139 | ```bash
140 | $ trap 'rm -f "$TMPFILE"' EXIT
141 | ```
142 |
143 | 上面命令中,脚本遇到`EXIT`信号时,就会执行`rm -f "$TMPFILE"`。
144 |
145 | trap 命令的常见使用场景,就是在 Bash 脚本中指定退出时执行的清理命令。
146 |
147 | ```bash
148 | #!/bin/bash
149 |
150 | trap 'rm -f "$TMPFILE"' EXIT
151 |
152 | TMPFILE=$(mktemp) || exit 1
153 | ls /etc > $TMPFILE
154 | if grep -qi "kernel" $TMPFILE; then
155 | echo 'find'
156 | fi
157 | ```
158 |
159 | 上面代码中,不管是脚本正常执行结束,还是用户按 Ctrl + C 终止,都会产生`EXIT`信号,从而触发删除临时文件。
160 |
161 | 注意,`trap`命令必须放在脚本的开头。否则,它上方的任何命令导致脚本退出,都不会被它捕获。
162 |
163 | 如果`trap`需要触发多条命令,可以封装一个 Bash 函数。
164 |
165 | ```bash
166 | function egress {
167 | command1
168 | command2
169 | command3
170 | }
171 |
172 | trap egress EXIT
173 | ```
174 |
175 | ## 参考链接
176 |
177 | - [Working with Temporary Files and Directories in Shell Scripts](https://www.putorius.net/working-with-temporary-files.html), Steven Vona
178 | - [Using Trap to Exit Bash Scripts Cleanly](https://www.putorius.net/using-trap-to-exit-bash-scripts-cleanly.html)
179 | - [Sending and Trapping Signals](https://mywiki.wooledge.org/SignalTrap)
180 |
181 |
--------------------------------------------------------------------------------
/docs/prompt.md:
--------------------------------------------------------------------------------
1 | # 命令提示符
2 |
3 | 用户进入 Bash 以后,Bash 会显示一个命令提示符,用来提示用户在该位置后面输入命令。
4 |
5 | ## 环境变量 PS1
6 |
7 | 命令提示符通常是美元符号`$`,对于根用户则是井号`#`。这个符号是环境变量`PS1`决定的,执行下面的命令,可以看到当前命令提示符的定义。
8 |
9 | ```bash
10 | $ echo $PS1
11 | ```
12 |
13 | Bash 允许用户自定义命令提示符,只要改写这个变量即可。改写后的`PS1`,可以放在用户的 Bash 配置文件`.bashrc`里面,以后新建 Bash 对话时,新的提示符就会生效。要在当前窗口看到修改后的提示符,可以执行下面的命令。
14 |
15 | ```bash
16 | $ source ~/.bashrc
17 | ```
18 |
19 | 命令提示符的定义,可以包含特殊的转义字符,表示特定内容。
20 |
21 | - `\a`:响铃,计算机发出一记声音。
22 | - `\d`:以星期、月、日格式表示当前日期,例如“Mon May 26”。
23 | - `\h`:本机的主机名。
24 | - `\H`:完整的主机名。
25 | - `\j`:运行在当前 Shell 会话的工作数。
26 | - `\l`:当前终端设备名。
27 | - `\n`:一个换行符。
28 | - `\r`:一个回车符。
29 | - `\s`:Shell 的名称。
30 | - `\t`:24小时制的`hours:minutes:seconds`格式表示当前时间。
31 | - `\T`:12小时制的当前时间。
32 | - `\@`:12小时制的`AM/PM`格式表示当前时间。
33 | - `\A`:24小时制的`hours:minutes`表示当前时间。
34 | - `\u`:当前用户名。
35 | - `\v`:Shell 的版本号。
36 | - `\V`:Shell 的版本号和发布号。
37 | - `\w`:当前的工作路径。
38 | - `\W`:当前目录名。
39 | - `\!`:当前命令在命令历史中的编号。
40 | - `\#`:当前 shell 会话中的命令数。
41 | - `\$`:普通用户显示为`$`字符,根用户显示为`#`字符。
42 | - `\[`:非打印字符序列的开始标志。
43 | - `\]`:非打印字符序列的结束标志。
44 |
45 | 举例来说,`[\u@\h \W]\$`这个提示符定义,显示出来就是`[user@host ~]$`(具体的显示内容取决于你的系统)。
46 |
47 | ```bash
48 | [user@host ~]$ echo $PS1
49 | [\u@\h \W]\$
50 | ```
51 |
52 | 改写`PS1`变量,就可以改变这个命令提示符。
53 |
54 | ```bash
55 | $ PS1="\A \h \$ "
56 | 17:33 host $
57 | ```
58 |
59 | 注意,`$`后面最好跟一个空格,这样的话,用户的输入与提示符就不会连在一起。
60 |
61 | ## 颜色
62 |
63 | 默认情况下,命令提示符是显示终端预定义的颜色。Bash 允许自定义提示符颜色。
64 |
65 | 使用下面的代码,可以设定其后文本的颜色。
66 |
67 | - `\033[0;30m`:黑色
68 | - `\033[1;30m`:深灰色
69 | - `\033[0;31m`:红色
70 | - `\033[1;31m`:浅红色
71 | - `\033[0;32m`:绿色
72 | - `\033[1;32m`:浅绿色
73 | - `\033[0;33m`:棕色
74 | - `\033[1;33m`:黄色
75 | - `\033[0;34m`:蓝色
76 | - `\033[1;34m`:浅蓝色
77 | - `\033[0;35m`:粉红
78 | - `\033[1;35m`:浅粉色
79 | - `\033[0;36m`:青色
80 | - `\033[1;36m`:浅青色
81 | - `\033[0;37m`:浅灰色
82 | - `\033[1;37m`:白色
83 |
84 | 举例来说,如果要将提示符设为红色,可以将`PS1`设成下面的代码。
85 |
86 | ```bash
87 | PS1='\[\033[0;31m\]<\u@\h \W>\$'
88 | ```
89 |
90 | 但是,上面这样设置以后,用户在提示符后面输入的文本也是红色的。为了解决这个问题, 可以在结尾添加另一个特殊代码`\[\033[00m\]`,表示将其后的文本恢复到默认颜色。
91 |
92 | ```bash
93 | PS1='\[\033[0;31m\]<\u@\h \W>\$\[\033[00m\]'
94 | ```
95 |
96 | 除了设置前景颜色,Bash 还允许设置背景颜色。
97 |
98 | - `\033[0;40m`:蓝色
99 | - `\033[1;44m`:黑色
100 | - `\033[0;41m`:红色
101 | - `\033[1;45m`:粉红
102 | - `\033[0;42m`:绿色
103 | - `\033[1;46m`:青色
104 | - `\033[0;43m`:棕色
105 | - `\033[1;47m`:浅灰色
106 |
107 | 下面是一个带有红色背景的提示符。
108 |
109 | ```bash
110 | PS1='\[\033[0;41m\]<\u@\h \W>\$\[\033[0m\] '
111 | ```
112 |
113 | ## 环境变量 PS2,PS3,PS4
114 |
115 | 除了`PS1`,Bash 还提供了提示符相关的另外三个环境变量。
116 |
117 | 环境变量`PS2`是命令行折行输入时系统的提示符,默认为`> `。
118 |
119 | ```bash
120 | $ echo "hello
121 | > world"
122 | ```
123 |
124 | 上面命令中,输入`hello`以后按下回车键,系统会提示继续输入。这时,第二行显示的提示符就是`PS2`定义的`> `。
125 |
126 | 环境变量`PS3`是使用`select`命令时,系统输入菜单的提示符。
127 |
128 | 环境变量`PS4`默认为`+ `。它是使用 Bash 的`-x`参数执行脚本时,每一行命令在执行前都会先打印出来,并且在行首出现的那个提示符。
129 |
130 | 比如下面是脚本`test.sh`。
131 |
132 | ```bash
133 | #!/bin/bash
134 |
135 | echo "hello world"
136 | ```
137 |
138 | 使用`-x`参数执行这个脚本。
139 |
140 | ```bash
141 | $ bash -x test.sh
142 | + echo 'hello world'
143 | hello world
144 | ```
145 |
146 | 上面例子中,输出的第一行前面有一个`+ `,这就是变量`PS4`定义的。
147 |
148 |
--------------------------------------------------------------------------------
/docs/quotation.md:
--------------------------------------------------------------------------------
1 | # 引号和转义
2 |
3 | Bash 只有一种数据类型,就是字符串。不管用户输入什么数据,Bash 都视为字符串。因此,字符串相关的引号和转义,对 Bash 来说就非常重要。
4 |
5 | ## 转义
6 |
7 | 某些字符在 Bash 里面有特殊含义(比如`$`、`&`、`*`)。
8 |
9 | ```bash
10 | $ echo $date
11 |
12 | $
13 | ```
14 |
15 | 上面例子中,输出`$date`不会有任何结果,因为`$`是一个特殊字符。
16 |
17 | 如果想要原样输出这些特殊字符,就必须在它们前面加上反斜杠,使其变成普通字符。这就叫做“转义”(escape)。
18 |
19 | ```bash
20 | $ echo \$date
21 | $date
22 | ```
23 |
24 | 上面命令中,只有在特殊字符`$`前面加反斜杠,才能原样输出。
25 |
26 | 反斜杠本身也是特殊字符,如果想要原样输出反斜杠,就需要对它自身转义,连续使用两个反斜线(`\\`)。
27 |
28 | ```bash
29 | $ echo \\
30 | \
31 | ```
32 |
33 | 上面例子输出了反斜杠本身。
34 |
35 | 反斜杠除了用于转义,还可以表示一些不可打印的字符。
36 |
37 | - `\a`:响铃
38 | - `\b`:退格
39 | - `\n`:换行
40 | - `\r`:回车
41 | - `\t`:制表符
42 |
43 | 如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用`echo`命令的`-e`参数。
44 |
45 | ```bash
46 | $ echo a\tb
47 | atb
48 |
49 | $ echo -e "a\tb"
50 | a b
51 | ```
52 |
53 | 上面例子中,命令行直接输出不可打印字符`\t`,Bash 不能正确解释。必须把它们放在引号之中,然后使用`echo`命令的`-e`参数。
54 |
55 | 换行符是一个特殊字符,表示命令的结束,Bash 收到这个字符以后,就会对输入的命令进行解释执行。换行符前面加上反斜杠转义,就使得换行符变成一个普通字符,Bash 会将其当作长度为`0`的空字符处理,从而可以将一行命令写成多行。
56 |
57 | ```bash
58 | $ mv \
59 | /path/to/foo \
60 | /path/to/bar
61 |
62 | # 等同于
63 | $ mv /path/to/foo /path/to/bar
64 | ```
65 |
66 | 上面例子中,如果一条命令过长,就可以在行尾使用反斜杠,将其改写成多行。这是常见的多行命令的写法。
67 |
68 | ## 单引号
69 |
70 | Bash 允许字符串放在单引号或双引号之中,加以引用。
71 |
72 | 单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符,比如星号(`*`)、美元符号(`$`)、反斜杠(`\`)等。
73 |
74 | ```bash
75 | $ echo '*'
76 | *
77 |
78 | $ echo '$USER'
79 | $USER
80 |
81 | $ echo '$((2+2))'
82 | $((2+2))
83 |
84 | $ echo '$(echo foo)'
85 | $(echo foo)
86 | ```
87 |
88 | 上面命令中,单引号使得 Bash 扩展、变量引用、算术运算和子命令,都失效了。如果不使用单引号,它们都会被 Bash 自动扩展。
89 |
90 | 由于反斜杠在单引号里面变成了普通字符,所以如果单引号之中,还要使用单引号,不能使用转义,需要在外层的单引号前面加上一个美元符号(`$`),然后再对里层的单引号转义。
91 |
92 | ```bash
93 | # 不正确
94 | $ echo it's
95 |
96 | # 不正确
97 | $ echo 'it\'s'
98 |
99 | # 正确
100 | $ echo $'it\'s'
101 | ```
102 |
103 | 不过,更合理的方法是改在双引号之中使用单引号。
104 |
105 | ```bash
106 | $ echo "it's"
107 | it's
108 | ```
109 |
110 | ## 双引号
111 |
112 | 双引号比单引号宽松,大部分特殊字符在双引号里面,都会失去特殊含义,变成普通字符。
113 |
114 | ```bash
115 | $ echo "*"
116 | *
117 | ```
118 |
119 | 上面例子中,通配符`*`是一个特殊字符,放在双引号之中,就变成了普通字符,会原样输出。这一点需要特别留意,这意味着,双引号里面不会进行文件名扩展。
120 |
121 | 但是,三个特殊字符除外:美元符号(`$`)、反引号(`` ` ``)和反斜杠(`\`)。这三个字符在双引号之中,依然有特殊含义,会被 Bash 自动扩展。
122 |
123 | ```bash
124 | $ echo "$SHELL"
125 | /bin/bash
126 |
127 | $ echo "`date`"
128 | Mon Jan 27 13:33:18 CST 2020
129 | ```
130 |
131 | 上面例子中,美元符号(`$`)和反引号(`` ` ``)在双引号中,都保持特殊含义。美元符号用来引用变量,反引号则是执行子命令。
132 |
133 | ```bash
134 | $ echo "I'd say: \"hello.\""
135 | I'd say: "hello."
136 |
137 | $ echo "\\"
138 | \
139 | ```
140 |
141 | 上面例子中,反斜杠在双引号之中保持特殊含义,用来转义。所以,可以使用反斜杠,在双引号之中插入双引号,或者插入反斜杠本身。
142 |
143 | 换行符在双引号之中,会失去特殊含义,Bash 不再将其解释为命令的结束,只是作为普通的换行符。所以可以利用双引号,在命令行输入多行文本。
144 |
145 | ```bash
146 | $ echo "hello
147 | world"
148 | hello
149 | world
150 | ```
151 |
152 | 上面命令中,Bash 正常情况下会将换行符解释为命令结束,但是换行符在双引号之中就失去了这种特殊作用,只用来换行,所以可以输入多行。`echo`命令会将换行符原样输出,显示的时候正常解释为换行。
153 |
154 | 双引号的另一个常见的使用场合是,文件名包含空格。这时就必须使用双引号(或单引号),将文件名放在里面。
155 |
156 | ```bash
157 | $ ls "two words.txt"
158 | ```
159 |
160 | 上面命令中,`two words.txt`是一个包含空格的文件名,如果不放在双引号里面,就会被 Bash 当作两个文件。
161 |
162 | 双引号会原样保存多余的空格。
163 |
164 | ```bash
165 | $ echo "this is a test"
166 | this is a test
167 | ```
168 |
169 | 双引号还有一个作用,就是保存原始命令的输出格式。
170 |
171 | ```bash
172 | # 单行输出
173 | $ echo $(cal)
174 | 一月 2020 日 一 二 三 四 五 六 1 2 3 ... 31
175 |
176 | # 原始格式输出
177 | $ echo "$(cal)"
178 | 一月 2020
179 | 日 一 二 三 四 五 六
180 | 1 2 3 4
181 | 5 6 7 8 9 10 11
182 | 12 13 14 15 16 17 18
183 | 19 20 21 22 23 24 25
184 | 26 27 28 29 30 31
185 | ```
186 |
187 | 上面例子中,如果`$(cal)`不放在双引号之中,`echo`就会将所有结果以单行输出,丢弃了所有原始的格式。
188 |
189 | ## Here 文档
190 |
191 | Here 文档(here document)是一种输入多行字符串的方法,格式如下。
192 |
193 | ```bash
194 | << token
195 | text
196 | token
197 | ```
198 |
199 | 它的格式分成开始标记(`<< token`)和结束标记(`token`)。开始标记是两个小于号 + Here 文档的名称,名称可以随意取,后面必须是一个换行符;结束标记是单独一行顶格写的 Here 文档名称,如果不是顶格,结束标记不起作用。两者之间就是多行字符串的内容。
200 |
201 | 下面是一个通过 Here 文档输出 HTML 代码的例子。
202 |
203 | ```bash
204 | $ cat << _EOF_
205 |
206 |
207 |
208 | The title of your page
209 |
210 |
211 |
212 |
213 | Your page content goes here.
214 |
215 |
216 | _EOF_
217 | ```
218 |
219 | Here 文档内部会发生变量替换,同时支持反斜杠转义,但是不支持通配符扩展,双引号和单引号也失去语法作用,变成了普通字符。
220 |
221 | ```bash
222 | $ foo='hello world'
223 | $ cat << _example_
224 | $foo
225 | "$foo"
226 | '$foo'
227 | _example_
228 |
229 | hello world
230 | "hello world"
231 | 'hello world'
232 | ```
233 |
234 | 上面例子中,变量`$foo`发生了替换,但是双引号和单引号都原样输出了,表明它们已经失去了引用的功能。
235 |
236 | 如果不希望发生变量替换,可以把 Here 文档的开始标记放在单引号之中。
237 |
238 | ```bash
239 | $ foo='hello world'
240 | $ cat << '_example_'
241 | $foo
242 | "$foo"
243 | '$foo'
244 | _example_
245 |
246 | $foo
247 | "$foo"
248 | '$foo'
249 | ```
250 |
251 | 上面例子中,Here 文档的开始标记(`_example_`)放在单引号之中,导致变量替换失效了。
252 |
253 | Here 文档的本质是重定向,它将字符串重定向输出给某个命令,相当于包含了`echo`命令。
254 |
255 | ```bash
256 | $ command << token
257 | string
258 | token
259 |
260 | # 等同于
261 |
262 | $ echo string | command
263 | ```
264 |
265 | 上面代码中,Here 文档相当于`echo`命令的重定向。
266 |
267 | 所以,Here 字符串只适合那些可以接受标准输入作为参数的命令,对于其他命令无效,比如`echo`命令就不能用 Here 文档作为参数。
268 |
269 | ```bash
270 | $ echo << _example_
271 | hello
272 | _example_
273 | ```
274 |
275 | 上面例子不会有任何输出,因为 Here 文档对于`echo`命令无效。
276 |
277 | 此外,Here 文档也不能作为变量的值,只能用于命令的参数。
278 |
279 | ## Here 字符串
280 |
281 | Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(`<<<`)表示。
282 |
283 | ```bash
284 | <<< string
285 | ```
286 |
287 | 它的作用是将字符串通过标准输入,传递给命令。
288 |
289 | 有些命令直接接受给定的参数,与通过标准输入接受参数,结果是不一样的。所以才有了这个语法,使得将字符串通过标准输入传递给命令更方便,比如`cat`命令只接受标准输入传入的字符串。
290 |
291 | ```bash
292 | $ cat <<< 'hi there'
293 | # 等同于
294 | $ echo 'hi there' | cat
295 | ```
296 |
297 | 上面的第一种语法使用了 Here 字符串,要比第二种语法看上去语义更好,也更简洁。
298 |
299 | ```bash
300 | $ md5sum <<< 'ddd'
301 | # 等同于
302 | $ echo 'ddd' | md5sum
303 | ```
304 |
305 | 上面例子中,`md5sum`命令只能接受标准输入作为参数,不能直接将字符串放在命令后面,会被当作文件名,即`md5sum ddd`里面的`ddd`会被解释成文件名。这时就可以用 Here 字符串,将字符串传给`md5sum`命令。
306 |
307 |
--------------------------------------------------------------------------------
/docs/read.md:
--------------------------------------------------------------------------------
1 | # read 命令
2 |
3 | ## 用法
4 |
5 | 有时,脚本需要在执行过程中,由用户提供一部分数据,这时可以使用`read`命令。它将用户的输入存入一个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。
6 |
7 | `read`命令的格式如下。
8 |
9 | ```bash
10 | read [-options] [variable...]
11 | ```
12 |
13 | 上面语法中,`options`是参数选项,`variable`是用来保存输入数值的一个或多个变量名。如果没有提供变量名,环境变量`REPLY`会包含用户输入的一整行数据。
14 |
15 | 下面是一个例子`demo.sh`。
16 |
17 | ```bash
18 | #!/bin/bash
19 |
20 | echo -n "输入一些文本 > "
21 | read text
22 | echo "你的输入:$text"
23 | ```
24 |
25 | 上面例子中,先显示一行提示文本,然后会等待用户输入文本。用户输入的文本,存入变量`text`,在下一行显示出来。
26 |
27 | ```bash
28 | $ bash demo.sh
29 | 输入一些文本 > 你好,世界
30 | 你的输入:你好,世界
31 | ```
32 |
33 | `read`可以接受用户输入的多个值。
34 |
35 | ```bash
36 | #!/bin/bash
37 | echo Please, enter your firstname and lastname
38 | read FN LN
39 | echo "Hi! $LN, $FN !"
40 | ```
41 |
42 | 上面例子中,`read`根据用户的输入,同时为两个变量赋值。
43 |
44 | 如果用户的输入项少于`read`命令给出的变量数目,那么额外的变量值为空。如果用户的输入项多于定义的变量,那么多余的输入项会包含到最后一个变量中。
45 |
46 | 如果`read`命令之后没有定义变量名,那么环境变量`REPLY`会包含所有的输入。
47 |
48 | ```bash
49 | #!/bin/bash
50 | # read-single: read multiple values into default variable
51 | echo -n "Enter one or more values > "
52 | read
53 | echo "REPLY = '$REPLY'"
54 | ```
55 |
56 | 上面脚本的运行结果如下。
57 |
58 | ```bash
59 | $ read-single
60 | Enter one or more values > a b c d
61 | REPLY = 'a b c d'
62 | ```
63 |
64 | `read`命令除了读取键盘输入,可以用来读取文件。
65 |
66 | ```bash
67 | #!/bin/bash
68 |
69 | filename='/etc/hosts'
70 |
71 | while read myline
72 | do
73 | echo "$myline"
74 | done < $filename
75 | ```
76 |
77 | 上面的例子通过`read`命令,读取一个文件的内容。`done`命令后面的定向符`<`,将文件内容导向`read`命令,每次读取一行,存入变量`myline`,直到文件读取完毕。
78 |
79 | ## 参数
80 |
81 | `read`命令的参数如下。
82 |
83 | **(1)-t 参数**
84 |
85 | `read`命令的`-t`参数,设置了超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。
86 |
87 | ```bash
88 | #!/bin/bash
89 |
90 | echo -n "输入一些文本 > "
91 | if read -t 3 response; then
92 | echo "用户已经输入了"
93 | else
94 | echo "用户没有输入"
95 | fi
96 | ```
97 |
98 | 上面例子中,输入命令会等待3秒,如果用户超过这个时间没有输入,这个命令就会执行失败。`if`根据命令的返回值,转入`else`代码块,继续往下执行。
99 |
100 | 环境变量`TMOUT`也可以起到同样作用,指定`read`命令等待用户输入的时间(单位为秒)。
101 |
102 | ```bash
103 | $ TMOUT=3
104 | $ read response
105 | ```
106 |
107 | 上面例子也是等待3秒,如果用户还没有输入,就会超时。
108 |
109 | **(2)-p 参数**
110 |
111 | `-p`参数指定用户输入的提示信息。
112 |
113 | ```bash
114 | read -p "Enter one or more values > "
115 | echo "REPLY = '$REPLY'"
116 | ```
117 |
118 | 上面例子中,先显示`Enter one or more values >`,再接受用户的输入。
119 |
120 | **(3)-a 参数**
121 |
122 | `-a`参数把用户的输入赋值给一个数组,从零号位置开始。
123 |
124 | ```bash
125 | $ read -a people
126 | alice duchess dodo
127 | $ echo ${people[2]}
128 | dodo
129 | ```
130 |
131 | 上面例子中,用户输入被赋值给一个数组`people`,这个数组的2号成员就是`dodo`。
132 |
133 | **(4)-n 参数**
134 |
135 | `-n`参数指定只读取若干个字符作为变量值,而不是整行读取。
136 |
137 | ```bash
138 | $ read -n 3 letter
139 | abcdefghij
140 | $ echo $letter
141 | abc
142 | ```
143 |
144 | 上面例子中,变量`letter`只包含3个字母。
145 |
146 | **(5)-e 参数**
147 |
148 | `-e`参数允许用户输入的时候,使用`readline`库提供的快捷键,比如自动补全。具体的快捷键可以参阅《行操作》一章。
149 |
150 | ```bash
151 | #!/bin/bash
152 |
153 | echo Please input the path to the file:
154 |
155 | read -e fileName
156 |
157 | echo $fileName
158 | ```
159 |
160 | 上面例子中,`read`命令接受用户输入的文件名。这时,用户可能想使用 Tab 键的文件名“自动补全”功能,但是`read`命令的输入默认不支持`readline`库的功能。`-e`参数就可以允许用户使用自动补全。
161 |
162 | **(6)其他参数**
163 |
164 | - `-d delimiter`:定义字符串`delimiter`的第一个字符作为用户输入的结束,而不是一个换行符。
165 | - `-r`:raw 模式,表示不把用户输入的反斜杠字符解释为转义字符。
166 | - `-s`:使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。
167 | - `-u fd`:使用文件描述符`fd`作为输入。
168 |
169 | ## IFS 变量
170 |
171 | `read`命令读取的值,默认是以空格分隔。可以通过自定义环境变量`IFS`(内部字段分隔符,Internal Field Separator 的缩写),修改分隔标志。
172 |
173 | `IFS`的默认值是空格、Tab 符号、换行符号,通常取第一个(即空格)。
174 |
175 | 如果把`IFS`定义成冒号(`:`)或分号(`;`),就可以分隔以这两个符号分隔的值,这对读取文件很有用。
176 |
177 | ```bash
178 | #!/bin/bash
179 | # read-ifs: read fields from a file
180 |
181 | FILE=/etc/passwd
182 |
183 | read -p "Enter a username > " user_name
184 | file_info="$(grep "^$user_name:" $FILE)"
185 |
186 | if [ -n "$file_info" ]; then
187 | IFS=":" read user pw uid gid name home shell <<< "$file_info"
188 | echo "User = '$user'"
189 | echo "UID = '$uid'"
190 | echo "GID = '$gid'"
191 | echo "Full Name = '$name'"
192 | echo "Home Dir. = '$home'"
193 | echo "Shell = '$shell'"
194 | else
195 | echo "No such user '$user_name'" >&2
196 | exit 1
197 | fi
198 | ```
199 |
200 | 上面例子中,`IFS`设为冒号,然后用来分解`/etc/passwd`文件的一行。`IFS`的赋值命令和`read`命令写在一行,这样的话,`IFS`的改变仅对后面的命令生效,该命令执行后`IFS`会自动恢复原来的值。如果不写在一行,就要采用下面的写法。
201 |
202 | ```bash
203 | OLD_IFS="$IFS"
204 | IFS=":"
205 | read user pw uid gid name home shell <<< "$file_info"
206 | IFS="$OLD_IFS"
207 | ```
208 |
209 | 另外,上面例子中,`<<<`是 Here 字符串,用于将变量值转为标准输入,因为`read`命令只能解析标准输入。
210 |
211 | 如果`IFS`设为空字符串,就等同于将整行读入一个变量。
212 |
213 | ```bash
214 | #!/bin/bash
215 | input="/path/to/txt/file"
216 | while IFS= read -r line
217 | do
218 | echo "$line"
219 | done < "$input"
220 | ```
221 |
222 | 上面的命令可以逐行读取文件,每一行存入变量`line`,打印出来以后再读取下一行。
223 |
224 |
--------------------------------------------------------------------------------
/docs/readline.md:
--------------------------------------------------------------------------------
1 | # Bash 行操作
2 |
3 | ## 简介
4 |
5 | Bash 内置了 Readline 库,具有这个库提供的很多“行操作”功能,比如命令的自动补全,可以大大加快操作速度。
6 |
7 | 这个库默认采用 Emacs 快捷键,也可以改成 Vi 快捷键。
8 |
9 | ```bash
10 | $ set -o vi
11 | ```
12 |
13 | 下面的命令可以改回 Emacs 快捷键。
14 |
15 | ```bash
16 | $ set -o emacs
17 | ```
18 |
19 | 如果想永久性更改编辑模式(Emacs / Vi),可以将命令写在`~/.inputrc`文件,这个文件是 Readline 的配置文件。
20 |
21 | ```bash
22 | set editing-mode vi
23 | ```
24 |
25 | 本章介绍的快捷键都属于 Emacs 模式。Vi 模式的快捷键,读者可以参考 Vi 编辑器的教程。
26 |
27 | Bash 默认开启这个库,但是允许关闭。
28 |
29 | ```bash
30 | $ bash --noediting
31 | ```
32 |
33 | 上面命令中,`--noediting`参数关闭了 Readline 库,启动的 Bash 就不带有行操作功能。
34 |
35 | ## 光标移动
36 |
37 | Readline 提供快速移动光标的快捷键。
38 |
39 | - `Ctrl + a`:移到行首。
40 | - `Ctrl + b`:向行首移动一个字符,与左箭头作用相同。
41 | - `Ctrl + e`:移到行尾。
42 | - `Ctrl + f`:向行尾移动一个字符,与右箭头作用相同。
43 | - `Alt + f`:移动到当前单词的词尾。
44 | - `Alt + b`:移动到当前单词的词首。
45 |
46 | 上面快捷键的 Alt 键,也可以用 ESC 键代替。
47 |
48 | ## 清除屏幕
49 |
50 | `Ctrl + l`快捷键可以清除屏幕,即将当前行移到屏幕的第一行,与`clear`命令作用相同。
51 |
52 | ## 编辑操作
53 |
54 | 下面的快捷键可以编辑命令行内容。
55 |
56 | - `Ctrl + d`:删除光标位置的字符(delete)。
57 | - `Ctrl + w`:删除光标前面的单词。
58 | - `Ctrl + t`:光标位置的字符与它前面一位的字符交换位置(transpose)。
59 | - `Alt + t`:光标位置的词与它前面一位的词交换位置(transpose)。
60 | - `Alt + l`:将光标位置至词尾转为小写(lowercase)。
61 | - `Alt + u`:将光标位置至词尾转为大写(uppercase)。
62 |
63 | 使用`Ctrl + d`的时候,如果当前行没有任何字符,会导致退出当前 Shell,所以要小心。
64 |
65 | 剪切和粘贴快捷键如下。
66 |
67 | - `Ctrl + k`:剪切光标位置到行尾的文本。
68 | - `Ctrl + u`:剪切光标位置到行首的文本。
69 | - `Alt + d`:剪切光标位置到词尾的文本。
70 | - `Alt + Backspace`:剪切光标位置到词首的文本。
71 | - `Ctrl + y`:在光标位置粘贴文本。
72 |
73 | 同样地,Alt 键可以用 Esc 键代替。
74 |
75 | ## 自动补全
76 |
77 | 命令输入到一半的时候,可以按一下 Tab 键,Readline 会自动补全命令或路径。比如,输入`cle`,再按下 Tab 键,Bash 会自动将这个命令补全为`clear`。
78 |
79 | 如果符合条件的命令或路径有多个,就需要连续按两次 Tab 键,Bash 会提示所有符合条件的命令或路径。
80 |
81 | 除了命令或路径,Tab 还可以补全其他值。如果一个值以`$`开头,则按下 Tab 键会补全变量;如果以`~`开头,则补全用户名;如果以`@`开头,则补全主机名(hostname),主机名以列在`/etc/hosts`文件里面的主机为准。
82 |
83 | 自动补全相关的快捷键如下。
84 |
85 | - Tab:完成自动补全。
86 | - `Alt + ?`:列出可能的补全,与连按两次 Tab 键作用相同。
87 | - `Alt + /`:尝试文件路径补全。
88 | - `Ctrl + x /`:先按`Ctrl + x`,再按`/`,等同于`Alt + ?`,列出可能的文件路径补全。
89 | - `Alt + !`:命令补全。
90 | - `Ctrl + x !`:先按`Ctrl + x`,再按`!`,等同于`Alt + !`,命令补全。
91 | - `Alt + ~`:用户名补全。
92 | - `Ctrl + x ~`:先按`Ctrl + x`,再按`~`,等同于`Alt + ~`,用户名补全。
93 | - `Alt + $`:变量名补全。
94 | - `Ctrl + x $`:先按`Ctrl + x`,再按`$`,等同于`Alt + $`,变量名补全。
95 | - `Alt + @`:主机名补全。
96 | - `Ctrl + x @`:先按`Ctrl + x`,再按`@`,等同于`Alt + @`,主机名补全。
97 | - `Alt + *`:在命令行一次性插入所有可能的补全。
98 | - `Alt + Tab`:尝试用`.bash_history`里面以前执行命令,进行补全。
99 |
100 | 上面的`Alt`键也可以用 ESC 键代替。
101 |
102 | ## 其他快捷键
103 |
104 | - `Ctrl + j`:等同于回车键(LINEFEED)。
105 | - `Ctrl + m`:等同于回车键(CARRIAGE RETURN)。
106 | - `Ctrl + o`:等同于回车键,并展示操作历史的下一个命令。
107 | - `Ctrl + v`:将下一个输入的特殊字符变成字面量,比如回车变成`^M`。
108 | - `Ctrl + [`:等同于 ESC。
109 | - `Alt + .`:插入上一个命令的最后一个词。
110 | - `Alt + _`:等同于`Alt + .`。
111 |
112 | 上面的`Alt + .`快捷键,对于很长的文件路径,有时会非常方便。因为 Unix 命令的最后一个参数通常是文件路径。
113 |
114 | ```bash
115 | $ mkdir foo_bar
116 | $ cd #按下 Alt + .
117 | ```
118 |
119 | 上面例子中,在`cd`命令后按下`Alt + .`,就会自动插入`foo_bar`。
120 |
121 |
--------------------------------------------------------------------------------
/docs/script.md:
--------------------------------------------------------------------------------
1 | # Bash 脚本入门
2 |
3 | 脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样。所有能够在命令行完成的任务,都能够用脚本完成。
4 |
5 | 脚本的好处是可以重复使用,也可以指定在特定场合自动调用,比如系统启动或关闭时自动执行脚本。
6 |
7 | ## Shebang 行
8 |
9 | 脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以`#!`字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。
10 |
11 | `#!`后面就是脚本解释器的位置,Bash 脚本的解释器一般是`/bin/sh`或`/bin/bash`。
12 |
13 | ```bash
14 | #!/bin/sh
15 | # 或者
16 | #!/bin/bash
17 | ```
18 |
19 | `#!`与脚本解释器之间有没有空格,都是可以的。
20 |
21 | 如果 Bash 解释器不放在目录`/bin`,脚本就无法执行了。为了保险,可以写成下面这样。
22 |
23 | ```bash
24 | #!/usr/bin/env bash
25 | ```
26 |
27 | 上面命令使用`env`命令(这个命令总是在`/usr/bin`目录),返回 Bash 可执行文件的位置。`env`命令的详细介绍,请看后文。
28 |
29 | Shebang 行不是必需的,但是建议加上这行。如果缺少该行,就需要手动将脚本传给解释器。举例来说,脚本是`script.sh`,有 Shebang 行的时候,可以直接调用执行。
30 |
31 | ```bash
32 | $ ./script.sh
33 | ```
34 |
35 | 上面例子中,`script.sh`是脚本文件名。脚本通常使用`.sh`后缀名,不过这不是必需的。
36 |
37 | 如果没有 Shebang 行,就只能手动将脚本传给解释器来执行。
38 |
39 | ```bash
40 | $ /bin/sh ./script.sh
41 | # 或者
42 | $ bash ./script.sh
43 | ```
44 |
45 | ## 执行权限和路径
46 |
47 | 前面说过,只要指定了 Shebang 行的脚本,可以直接执行。这有一个前提条件,就是脚本需要有执行权限。可以使用下面的命令,赋予脚本执行权限。
48 |
49 | ```bash
50 | # 给所有用户执行权限
51 | $ chmod +x script.sh
52 |
53 | # 给所有用户读权限和执行权限
54 | $ chmod +rx script.sh
55 | # 或者
56 | $ chmod 755 script.sh
57 |
58 | # 只给脚本拥有者读权限和执行权限
59 | $ chmod u+rx script.sh
60 | ```
61 |
62 | 脚本的权限通常设为`755`(拥有者有所有权限,其他人有读和执行权限)或者`700`(只有拥有者可以执行)。
63 |
64 | 除了执行权限,脚本调用时,一般需要指定脚本的路径(比如`path/script.sh`)。如果将脚本放在环境变量`$PATH`指定的目录中,就不需要指定路径了。因为 Bash 会自动到这些目录中,寻找是否存在同名的可执行文件。
65 |
66 | 建议在主目录新建一个`~/bin`子目录,专门存放可执行脚本,然后把`~/bin`加入`$PATH`。
67 |
68 | ```bash
69 | export PATH=$PATH:~/bin
70 | ```
71 |
72 | 上面命令改变环境变量`$PATH`,将`~/bin`添加到`$PATH`的末尾。可以将这一行加到`~/.bashrc`文件里面,然后重新加载一次`.bashrc`,这个配置就可以生效了。
73 |
74 | ```bash
75 | $ source ~/.bashrc
76 | ```
77 |
78 | 以后不管在什么目录,直接输入脚本文件名,脚本就会执行。
79 |
80 | ```bash
81 | $ script.sh
82 | ```
83 |
84 | 上面命令没有指定脚本路径,因为`script.sh`在`$PATH`指定的目录中。
85 |
86 | ## env 命令
87 |
88 | `env`命令总是指向`/usr/bin/env`文件,或者说,这个二进制文件总是在目录`/usr/bin`。
89 |
90 | `#!/usr/bin/env NAME`这个语法的意思是,让 Shell 查找`$PATH`环境变量里面第一个匹配的`NAME`。如果你不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。
91 |
92 | `/usr/bin/env bash`的意思就是,返回`bash`可执行文件的位置,前提是`bash`的路径是在`$PATH`里面。其他脚本文件也可以使用这个命令。比如 Node.js 脚本的 Shebang 行,可以写成下面这样。
93 |
94 | ```bash
95 | #!/usr/bin/env node
96 | ```
97 |
98 | `env`命令的参数如下。
99 |
100 | - `-i`, `--ignore-environment`:不带环境变量启动。
101 | - `-u`, `--unset=NAME`:从环境变量中删除一个变量。
102 | - `--help`:显示帮助。
103 | - `--version`:输出版本信息。
104 |
105 | 下面是一个例子,新建一个不带任何环境变量的 Shell。
106 |
107 | ```bash
108 | $ env -i /bin/sh
109 | ```
110 |
111 | ## 注释
112 |
113 | Bash 脚本中,`#`表示注释,可以放在行首,也可以放在行尾。
114 |
115 | ```bash
116 | # 本行是注释
117 | echo 'Hello World!'
118 |
119 | echo 'Hello World!' # 井号后面的部分也是注释
120 | ```
121 |
122 | 建议在脚本开头,使用注释说明当前脚本的作用,这样有利于日后的维护。
123 |
124 | ## 脚本参数
125 |
126 | 调用脚本的时候,脚本文件名后面可以带有参数。
127 |
128 | ```bash
129 | $ script.sh word1 word2 word3
130 | ```
131 |
132 | 上面例子中,`script.sh`是一个脚本文件,`word1`、`word2`和`word3`是三个参数。
133 |
134 | 脚本文件内部,可以使用特殊变量,引用这些参数。
135 |
136 | - `$0`:脚本文件名,即`script.sh`。
137 | - `$1`~`$9`:对应脚本的第一个参数到第九个参数。
138 | - `$#`:参数的总数。
139 | - `$@`:全部的参数,参数之间使用空格分隔。
140 | - `$*`:全部的参数,参数之间使用变量`$IFS`值的第一个字符分隔,默认为空格,但是可以自定义。
141 |
142 | 如果脚本的参数多于9个,那么第10个参数可以用`${10}`的形式引用,以此类推。
143 |
144 | 注意,如果命令是`command -o foo bar`,那么`-o`是`$1`,`foo`是`$2`,`bar`是`$3`。
145 |
146 | 下面是一个脚本内部读取命令行参数的例子。
147 |
148 | ```bash
149 | #!/bin/bash
150 | # script.sh
151 |
152 | echo "全部参数:" $@
153 | echo "命令行参数数量:" $#
154 | echo '$0 = ' $0
155 | echo '$1 = ' $1
156 | echo '$2 = ' $2
157 | echo '$3 = ' $3
158 | ```
159 |
160 | 执行结果如下。
161 |
162 | ```bash
163 | $ ./script.sh a b c
164 | 全部参数:a b c
165 | 命令行参数数量:3
166 | $0 = script.sh
167 | $1 = a
168 | $2 = b
169 | $3 = c
170 | ```
171 |
172 | 用户可以输入任意数量的参数,利用`for`循环,可以读取每一个参数。
173 |
174 | ```bash
175 | #!/bin/bash
176 |
177 | for i in "$@"; do
178 | echo $i
179 | done
180 | ```
181 |
182 | 上面例子中,`$@`返回一个全部参数的列表,然后使用`for`循环遍历。
183 |
184 | 如果多个参数放在双引号里面,视为一个参数。
185 |
186 | ```bash
187 | $ ./script.sh "a b"
188 | ```
189 |
190 | 上面例子中,Bash 会认为`"a b"`是一个参数,`$1`会返回`a b`。注意,返回时不包括双引号。
191 |
192 | ## shift 命令
193 |
194 | `shift`命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数(`$1`),使得后面的参数向前一位,即`$2`变成`$1`、`$3`变成`$2`、`$4`变成`$3`,以此类推。
195 |
196 | `while`循环结合`shift`命令,也可以读取每一个参数。
197 |
198 | ```bash
199 | #!/bin/bash
200 |
201 | echo "一共输入了 $# 个参数"
202 |
203 | while [ "$1" != "" ]; do
204 | echo "剩下 $# 个参数"
205 | echo "参数:$1"
206 | shift
207 | done
208 | ```
209 |
210 | 上面例子中,`shift`命令每次移除当前第一个参数,从而通过`while`循环遍历所有参数。
211 |
212 | `shift`命令可以接受一个整数作为参数,指定所要移除的参数个数,默认为`1`。
213 |
214 | ```bash
215 | shift 3
216 | ```
217 |
218 | 上面的命令移除前三个参数,原来的`$4`变成`$1`。
219 |
220 | ## getopts 命令
221 |
222 | `getopts`命令用在脚本内部,可以解析复杂的脚本命令行参数,通常与`while`循环一起使用,取出脚本所有的带有前置连词线(`-`)的参数。
223 |
224 | ```bash
225 | getopts optstring name
226 | ```
227 |
228 | 它带有两个参数。第一个参数`optstring`是字符串,给出脚本所有的连词线参数。比如,某个脚本可以有三个配置项参数`-l`、`-h`、`-a`,其中只有`-a`可以带有参数值,而`-l`和`-h`是开关参数,那么`getopts`的第一个参数写成`lha:`,顺序不重要。注意,`a`后面有一个冒号,表示该参数带有参数值,`getopts`规定带有参数值的配置项参数,后面必须带有一个冒号(`:`)。`getopts`的第二个参数`name`是一个变量名,用来保存当前取到的配置项参数,即`l`、`h`或`a`。
229 |
230 | 下面是一个例子。
231 |
232 | ```bash
233 | while getopts 'lha:' OPTION; do
234 | case "$OPTION" in
235 | l)
236 | echo "linuxconfig"
237 | ;;
238 |
239 | h)
240 | echo "h stands for h"
241 | ;;
242 |
243 | a)
244 | avalue="$OPTARG"
245 | echo "The value provided is $OPTARG"
246 | ;;
247 | ?)
248 | echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
249 | exit 1
250 | ;;
251 | esac
252 | done
253 | shift "$(($OPTIND - 1))"
254 | ```
255 |
256 | 上面例子中,`while`循环不断执行`getopts 'lha:' OPTION`命令,每次执行就会读取一个连词线参数(以及对应的参数值),然后进入循环体。变量`OPTION`保存的是,当前处理的那一个连词线参数(即`l`、`h`或`a`)。如果用户输入了没有指定的参数(比如`-x`),那么`OPTION`等于`?`。循环体内使用`case`判断,处理这四种不同的情况。
257 |
258 | 如果某个连词线参数带有参数值,比如`-a foo`,那么处理`a`参数的时候,环境变量`$OPTARG`保存的就是参数值。
259 |
260 | 注意,只要遇到不带连词线的参数,`getopts`就会执行失败,从而退出`while`循环。比如,`getopts`可以解析`command -l foo`,但不可以解析`command foo -l`。另外,多个连词线参数写在一起的形式,比如`command -lh`,`getopts`也可以正确处理。
261 |
262 | 变量`$OPTIND`在`getopts`开始执行前是`1`,然后每次执行就会加`1`。等到退出`while`循环,就意味着连词线参数全部处理完毕。这时,`$OPTIND - 1`就是已经处理的连词线参数个数,使用`shift`命令将这些参数移除,保证后面的代码可以用`$1`、`$2`等处理命令的主参数。
263 |
264 | ## 配置项参数终止符 `--`
265 |
266 | `-`和`--`开头的参数,会被 Bash 当作配置项解释。但是,有时它们不是配置项,而是实体参数的一部分,比如文件名叫做`-f`或`--file`。
267 |
268 | ```bash
269 | $ cat -f
270 | $ cat --file
271 | ```
272 |
273 | 上面命令的原意是输出文件`-f`和`--file`的内容,但是会被 Bash 当作配置项解释。
274 |
275 | 这时就可以使用配置项参数终止符`--`,它的作用是告诉 Bash,在它后面的参数开头的`-`和`--`不是配置项,只能当作实体参数解释。
276 |
277 | ```bash
278 | $ cat -- -f
279 | $ cat -- --file
280 | ```
281 |
282 | 上面命令可以正确展示文件`-f`和`--file`的内容,因为它们放在`--`的后面,开头的`-`和`--`就不再当作配置项解释了。
283 |
284 | 如果要确保某个变量不会被当作配置项解释,就要在它前面放上参数终止符`--`。
285 |
286 | ```bash
287 | $ ls -- $myPath
288 | ```
289 |
290 | 上面示例中,`--`强制变量`$myPath`只能当作实体参数(即路径名)解释。如果变量不是路径名,就会报错。
291 |
292 | ```bash
293 | $ myPath="-l"
294 | $ ls -- $myPath
295 | ls: 无法访问'-l': 没有那个文件或目录
296 | ```
297 |
298 | 上面例子中,变量`myPath`的值为`-l`,不是路径。但是,`--`强制`$myPath`只能作为路径解释,导致报错“不存在该路径”。
299 |
300 | 下面是另一个实际的例子,如果想在文件里面搜索`--hello`,这时也要使用参数终止符`--`。
301 |
302 | ```bash
303 | $ grep -- "--hello" example.txt
304 | ```
305 |
306 | 上面命令在`example.txt`文件里面,搜索字符串`--hello`。这个字符串是`--`开头,如果不用参数终止符,`grep`命令就会把`--hello`当作配置项参数,从而报错。
307 |
308 | ## exit 命令
309 |
310 | `exit`命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。
311 |
312 | ```bash
313 | $ exit
314 | ```
315 |
316 | 上面命令中止当前脚本,将最后一条命令的退出状态,作为整个脚本的退出状态。
317 |
318 | `exit`命令后面可以跟参数,该参数就是退出状态。
319 |
320 | ```bash
321 | # 退出值为0(成功)
322 | $ exit 0
323 |
324 | # 退出值为1(失败)
325 | $ exit 1
326 | ```
327 |
328 | 退出时,脚本会返回一个退出值。脚本的退出值,`0`表示正常,`1`表示发生错误,`2`表示用法不对,`126`表示不是可执行脚本,`127`表示命令没有发现。如果脚本被信号`N`终止,则退出值为`128 + N`。简单来说,只要退出值非0,就认为执行出错。
329 |
330 | 下面是一个例子。
331 |
332 | ```bash
333 | if [ $(id -u) != "0" ]; then
334 | echo "根用户才能执行当前脚本"
335 | exit 1
336 | fi
337 | ```
338 |
339 | 上面的例子中,`id -u`命令返回用户的 ID,一旦用户的 ID 不等于`0`(根用户的 ID),脚本就会退出,并且退出码为`1`,表示运行失败。
340 |
341 | `exit`与`return`命令的差别是,`return`命令是函数的退出,并返回一个值给调用者,脚本依然执行。`exit`是整个脚本的退出,如果在函数之中调用`exit`,则退出函数,并终止脚本执行。
342 |
343 | ## 命令执行结果
344 |
345 | 命令执行结束后,会有一个返回值。`0`表示执行成功,非`0`(通常是`1`)表示执行失败。环境变量`$?`可以读取前一个命令的返回值。
346 |
347 | 利用这一点,可以在脚本中对命令执行结果进行判断。
348 |
349 | ```bash
350 | cd /path/to/somewhere
351 | if [ "$?" = "0" ]; then
352 | rm *
353 | else
354 | echo "无法切换目录!" 1>&2
355 | exit 1
356 | fi
357 | ```
358 |
359 | 上面例子中,`cd /path/to/somewhere`这个命令如果执行成功(返回值等于`0`),就删除该目录里面的文件,否则退出脚本,整个脚本的返回值变为`1`,表示执行失败。
360 |
361 | 由于`if`可以直接判断命令的执行结果,执行相应的操作,上面的脚本可以改写成下面的样子。
362 |
363 | ```bash
364 | if cd /path/to/somewhere; then
365 | rm *
366 | else
367 | echo "Could not change directory! Aborting." 1>&2
368 | exit 1
369 | fi
370 | ```
371 |
372 | 更简洁的写法是利用两个逻辑运算符`&&`(且)和`||`(或)。
373 |
374 | ```bash
375 | # 第一步执行成功,才会执行第二步
376 | cd /path/to/somewhere && rm *
377 |
378 | # 第一步执行失败,才会执行第二步
379 | cd /path/to/somewhere || exit 1
380 | ```
381 |
382 | ## source 命令
383 |
384 | `source`命令用于执行一个脚本,通常用于重新加载一个配置文件。
385 |
386 | ```bash
387 | $ source .bashrc
388 | ```
389 |
390 | `source`命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell。所以,`source`命令执行脚本时,不需要`export`变量。
391 |
392 | ```bash
393 | #!/bin/bash
394 | # test.sh
395 | echo $foo
396 | ```
397 |
398 | 上面脚本输出`$foo`变量的值。
399 |
400 | ```bash
401 | # 当前 Shell 新建一个变量 foo
402 | $ foo=1
403 |
404 | # 打印输出 1
405 | $ source test.sh
406 | 1
407 |
408 | # 打印输出空字符串
409 | $ bash test.sh
410 | ```
411 |
412 | 上面例子中,当前 Shell 的变量`foo`并没有`export`,所以直接执行无法读取,但是`source`执行可以读取。
413 |
414 | `source`命令的另一个用途,是在脚本内部加载外部库。
415 |
416 | ```bash
417 | #!/bin/bash
418 |
419 | source ./lib.sh
420 |
421 | function_from_lib
422 | ```
423 |
424 | 上面脚本在内部使用`source`命令加载了一个外部库,然后就可以在脚本里面,使用这个外部库定义的函数。
425 |
426 | `source`有一个简写形式,可以使用一个点(`.`)来表示。
427 |
428 | ```bash
429 | $ . .bashrc
430 | ```
431 |
432 | ## 别名,alias 命令
433 |
434 | `alias`命令用来为一个命令指定别名,这样更便于记忆。下面是`alias`的格式。
435 |
436 | ```bash
437 | alias NAME=DEFINITION
438 | ```
439 |
440 | 上面命令中,`NAME`是别名的名称,`DEFINITION`是别名对应的原始命令。注意,等号两侧不能有空格,否则会报错。
441 |
442 | 一个常见的例子是为`grep`命令起一个`search`的别名。
443 |
444 | ```bash
445 | alias search=grep
446 | ```
447 |
448 | `alias`也可以用来为长命令指定一个更短的别名。下面是通过别名定义一个`today`的命令。
449 |
450 | ```bash
451 | $ alias today='date +"%A, %B %-d, %Y"'
452 | $ today
453 | 星期一, 一月 6, 2020
454 | ```
455 |
456 | 有时为了防止误删除文件,可以指定`rm`命令的别名。
457 |
458 | ```bash
459 | $ alias rm='rm -i'
460 | ```
461 |
462 | 上面命令指定`rm`命令是`rm -i`,每次删除文件之前,都会让用户确认。
463 |
464 | `alias`定义的别名也可以接受参数,参数会直接传入原始命令。
465 |
466 | ```bash
467 | $ alias echo='echo It says: '
468 | $ echo hello world
469 | It says: hello world
470 | ```
471 |
472 | 上面例子中,别名定义了`echo`命令的前两个参数,等同于修改了`echo`命令的默认行为。
473 |
474 | 指定别名以后,就可以像使用其他命令一样使用别名。一般来说,都会把常用的别名写在`~/.bashrc`的末尾。另外,只能为命令定义别名,为其他部分(比如很长的路径)定义别名是无效的。
475 |
476 | 直接调用`alias`命令,可以显示所有别名。
477 |
478 | ```bash
479 | $ alias
480 | ```
481 |
482 | `unalias`命令可以解除别名。
483 |
484 | ```bash
485 | $ unalias lt
486 | ```
487 |
488 | ## 参考链接
489 |
490 | - [How to use getopts to parse a script options](https://linuxconfig.org/how-to-use-getopts-to-parse-a-script-options), Egidio Docile
491 |
492 |
--------------------------------------------------------------------------------
/docs/set.md:
--------------------------------------------------------------------------------
1 | # set 命令,shopt 命令
2 |
3 | `set`命令是 Bash 脚本的重要环节,却常常被忽视,导致脚本的安全性和可维护性出问题。本章介绍`set`的基本用法,帮助你写出更安全的 Bash 脚本。
4 |
5 | ## 简介
6 |
7 | 我们知道,Bash 执行脚本时,会创建一个子 Shell。
8 |
9 | ```bash
10 | $ bash script.sh
11 | ```
12 |
13 | 上面代码中,`script.sh`是在一个子 Shell 里面执行。这个子 Shell 就是脚本的执行环境,Bash 默认给定了这个环境的各种参数。
14 |
15 | `set`命令用来修改子 Shell 环境的运行参数,即定制环境。一共有十几个参数可以定制,[官方手册](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)有完整清单,本章介绍其中最常用的几个。
16 |
17 | 顺便提一下,如果命令行下不带任何参数,直接运行`set`,会显示所有的环境变量和 Shell 函数。
18 |
19 | ```bash
20 | $ set
21 | ```
22 |
23 | ## set -u
24 |
25 | 执行脚本时,如果遇到不存在的变量,Bash 默认忽略它。
26 |
27 | ```bash
28 | #!/usr/bin/env bash
29 |
30 | echo $a
31 | echo bar
32 | ```
33 |
34 | 上面代码中,`$a`是一个不存在的变量。执行结果如下。
35 |
36 | ```bash
37 | $ bash script.sh
38 |
39 | bar
40 | ```
41 |
42 | 可以看到,`echo $a`输出了一个空行,Bash 忽略了不存在的`$a`,然后继续执行`echo bar`。大多数情况下,这不是开发者想要的行为,遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。
43 |
44 | `set -u`就用来改变这种行为。脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。
45 |
46 | ```bash
47 | #!/usr/bin/env bash
48 | set -u
49 |
50 | echo $a
51 | echo bar
52 | ```
53 |
54 | 运行结果如下。
55 |
56 | ```bash
57 | $ bash script.sh
58 | bash: script.sh:行4: a: 未绑定的变量
59 | ```
60 |
61 | 可以看到,脚本报错了,并且不再执行后面的语句。
62 |
63 | `-u`还有另一种写法`-o nounset`,两者是等价的。
64 |
65 | ```bash
66 | set -o nounset
67 | ```
68 |
69 | ## set -x
70 |
71 | 默认情况下,脚本执行后,只输出运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。
72 |
73 | `set -x`用来在运行结果之前,先输出执行的那一行命令。
74 |
75 | ```bash
76 | #!/usr/bin/env bash
77 | set -x
78 |
79 | echo bar
80 | ```
81 |
82 | 执行上面的脚本,结果如下。
83 |
84 | ```bash
85 | $ bash script.sh
86 | + echo bar
87 | bar
88 | ```
89 |
90 | 可以看到,执行`echo bar`之前,该命令会先打印出来,行首以`+`表示。这对于调试复杂的脚本是很有用的。
91 |
92 | `-x`还有另一种写法`-o xtrace`。
93 |
94 | ```bash
95 | set -o xtrace
96 | ```
97 |
98 | 脚本当中如果要关闭命令输出,可以使用`set +x`。
99 |
100 | ```bash
101 | #!/bin/bash
102 |
103 | number=1
104 |
105 | set -x
106 | if [ $number = "1" ]; then
107 | echo "Number equals 1"
108 | else
109 | echo "Number does not equal 1"
110 | fi
111 | set +x
112 | ```
113 |
114 | 上面的例子中,只对特定的代码段打开命令输出。
115 |
116 | ## Bash 的错误处理
117 |
118 | 如果脚本里面有运行失败的命令(返回值非`0`),Bash 默认会继续执行后面的命令。
119 |
120 | ```bash
121 | #!/usr/bin/env bash
122 |
123 | foo
124 | echo bar
125 | ```
126 |
127 | 上面脚本中,`foo`是一个不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。
128 |
129 | ```bash
130 | $ bash script.sh
131 | script.sh:行3: foo: 未找到命令
132 | bar
133 | ```
134 |
135 | 可以看到,Bash 只是显示有错误,并没有终止执行。
136 |
137 | 这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。这时,一般采用下面的写法。
138 |
139 | ```bash
140 | command || exit 1
141 | ```
142 |
143 | 上面的写法表示只要`command`有非零返回值,脚本就会停止执行。
144 |
145 | 如果停止执行之前需要完成多个操作,就要采用下面三种写法。
146 |
147 | ```bash
148 | # 写法一
149 | command || { echo "command failed"; exit 1; }
150 |
151 | # 写法二
152 | if ! command; then echo "command failed"; exit 1; fi
153 |
154 | # 写法三
155 | command
156 | if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi
157 | ```
158 |
159 | 另外,除了停止执行,还有一种情况。如果两个命令有继承关系,只有第一个命令成功了,才能继续执行第二个命令,那么就要采用下面的写法。
160 |
161 | ```bash
162 | command1 && command2
163 | ```
164 |
165 | ## set -e
166 |
167 | 上面这些写法多少有些麻烦,容易疏忽。`set -e`从根本上解决了这个问题,它使得脚本只要发生错误,就终止执行。
168 |
169 | ```bash
170 | #!/usr/bin/env bash
171 | set -e
172 |
173 | foo
174 | echo bar
175 | ```
176 |
177 | 执行结果如下。
178 |
179 | ```bash
180 | $ bash script.sh
181 | script.sh:行4: foo: 未找到命令
182 | ```
183 |
184 | 可以看到,第4行执行失败以后,脚本就终止执行了。
185 |
186 | `set -e`根据返回值来判断,一个命令是否运行失败。但是,某些命令的非零返回值可能不表示失败,或者开发者希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭`set -e`,该命令执行结束后,再重新打开`set -e`。
187 |
188 | ```bash
189 | set +e
190 | command1
191 | command2
192 | set -e
193 | ```
194 |
195 | 上面代码中,`set +e`表示关闭`-e`选项,`set -e`表示重新打开`-e`选项。
196 |
197 | 还有一种方法是使用`command || true`,使得该命令即使执行失败,脚本也不会终止执行。
198 |
199 | ```bash
200 | #!/bin/bash
201 | set -e
202 |
203 | foo || true
204 | echo bar
205 | ```
206 |
207 | 上面代码中,`true`使得这一行语句总是会执行成功,后面的`echo bar`会执行。
208 |
209 | `-e`还有另一种写法`-o errexit`。
210 |
211 | ```bash
212 | set -o errexit
213 | ```
214 |
215 | ## set -o pipefail
216 |
217 | `set -e`有一个例外情况,就是不适用于管道命令。
218 |
219 | 所谓管道命令,就是多个子命令通过管道运算符(`|`)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,`set -e`就失效了。
220 |
221 | 请看下面这个例子。
222 |
223 | ```bash
224 | #!/usr/bin/env bash
225 | set -e
226 |
227 | foo | echo a
228 | echo bar
229 | ```
230 |
231 | 执行结果如下。
232 |
233 | ```bash
234 | $ bash script.sh
235 | a
236 | script.sh:行4: foo: 未找到命令
237 | bar
238 | ```
239 |
240 | 上面代码中,`foo`是一个不存在的命令,但是`foo | echo a`这个管道命令会执行成功,导致后面的`echo bar`会继续执行。
241 |
242 | `set -o pipefail`用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。
243 |
244 | ```bash
245 | #!/usr/bin/env bash
246 | set -eo pipefail
247 |
248 | foo | echo a
249 | echo bar
250 | ```
251 |
252 | 运行后,结果如下。
253 |
254 | ```bash
255 | $ bash script.sh
256 | a
257 | script.sh:行4: foo: 未找到命令
258 | ```
259 |
260 | 可以看到,`echo bar`没有执行。
261 |
262 | ## set -E
263 |
264 | 一旦设置了`-e`参数,会导致函数内的错误不会被`trap`命令捕获(参考《trap 命令》一章)。`-E`参数可以纠正这个行为,使得函数也能继承`trap`命令。
265 |
266 | ```bash
267 | #!/bin/bash
268 | set -e
269 |
270 | trap "echo ERR trap fired!" ERR
271 |
272 | myfunc()
273 | {
274 | # 'foo' 是一个不存在的命令
275 | foo
276 | }
277 |
278 | myfunc
279 | ```
280 |
281 | 上面示例中,`myfunc`函数内部调用了一个不存在的命令`foo`,导致执行这个函数会报错。
282 |
283 | ```bash
284 | $ bash test.sh
285 | test.sh:行9: foo:未找到命令
286 | ```
287 |
288 | 但是,由于设置了`set -e`,函数内部的报错并没有被`trap`命令捕获,需要加上`-E`参数才可以。
289 |
290 | ```bash
291 | #!/bin/bash
292 | set -Eeuo pipefail
293 |
294 | trap "echo ERR trap fired!" ERR
295 |
296 | myfunc()
297 | {
298 | # 'foo' 是一个不存在的命令
299 | foo
300 | }
301 |
302 | myfunc
303 | ```
304 |
305 | 执行上面这个脚本,就可以看到`trap`命令生效了。
306 |
307 | ```bash
308 | $ bash test.sh
309 | test.sh:行9: foo:未找到命令
310 | ERR trap fired!
311 | ```
312 |
313 | ## 其他参数
314 |
315 | `set`命令还有一些其他参数。
316 |
317 | - `set -n`:等同于`set -o noexec`,不运行命令,只检查语法是否正确。
318 | - `set -f`:等同于`set -o noglob`,表示不对通配符进行文件名扩展。
319 | - `set -v`:等同于`set -o verbose`,表示打印 Shell 接收到的每一行输入。
320 | - `set -o noclobber`:防止使用重定向运算符`>`覆盖已经存在的文件。
321 |
322 | 上面的`-f`和`-v`参数,可以分别使用`set +f`、`set +v`关闭。
323 |
324 | ## set 命令总结
325 |
326 | 上面重点介绍的`set`命令的几个参数,一般都放在一起使用。
327 |
328 | ```bash
329 | # 写法一
330 | set -Eeuxo pipefail
331 |
332 | # 写法二
333 | set -Eeux
334 | set -o pipefail
335 | ```
336 |
337 | 这两种写法建议放在所有 Bash 脚本的头部。
338 |
339 | 另一种办法是在执行 Bash 脚本的时候,从命令行传入这些参数。
340 |
341 | ```bash
342 | $ bash -euxo pipefail script.sh
343 | ```
344 |
345 | ## shopt 命令
346 |
347 | `shopt`命令用来调整 Shell 的参数,跟`set`命令的作用很类似。之所以会有这两个类似命令的主要原因是,`set`是从 Ksh 继承的,属于 POSIX 规范的一部分,而`shopt`是 Bash 特有的。
348 |
349 | 直接输入`shopt`可以查看所有参数,以及它们各自打开和关闭的状态。
350 |
351 | ```bash
352 | $ shopt
353 | ```
354 |
355 | `shopt`命令后面跟着参数名,可以查询该参数是否打开。
356 |
357 | ```bash
358 | $ shopt globstar
359 | globstar off
360 | ```
361 |
362 | 上面例子表示`globstar`参数默认是关闭的。
363 |
364 | **(1)-s**
365 |
366 | `-s`用来打开某个参数。
367 |
368 | ```bash
369 | $ shopt -s optionNameHere
370 | ```
371 |
372 | **(2)-u**
373 |
374 | `-u`用来关闭某个参数。
375 |
376 | ```bash
377 | $ shopt -u optionNameHere
378 | ```
379 |
380 | 举例来说,`histappend`这个参数表示退出当前 Shell 时,将操作历史追加到历史文件中。这个参数默认是打开的,如果使用下面的命令将其关闭,那么当前 Shell 的操作历史将替换掉整个历史文件。
381 |
382 | ```bash
383 | $ shopt -u histappend
384 | ```
385 |
386 | **(3)-q**
387 |
388 | `-q`的作用也是查询某个参数是否打开,但不是直接输出查询结果,而是通过命令的执行状态(`$?`)表示查询结果。如果状态为`0`,表示该参数打开;如果为`1`,表示该参数关闭。
389 |
390 | ```bash
391 | $ shopt -q globstar
392 | $ echo $?
393 | 1
394 | ```
395 |
396 | 上面命令查询`globstar`参数是否打开。返回状态为`1`,表示该参数是关闭的。
397 |
398 | 这个用法主要用于脚本,供`if`条件结构使用。下面例子是如果打开了这个参数,就执行`if`结构内部的语句。
399 |
400 | ```bash
401 | if (shopt -q globstar); then
402 | ...
403 | fi
404 | ```
405 |
406 | ## 参考链接
407 |
408 | - [The Set Builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html)
409 | - [Safer bash scripts with 'set -euxo pipefail'](https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/)
410 | - [Writing Robust Bash Shell Scripts](http://www.davidpashley.com/articles/writing-robust-shell-scripts/)
411 |
412 |
--------------------------------------------------------------------------------
/docs/stack.md:
--------------------------------------------------------------------------------
1 | # 目录堆栈
2 |
3 | 为了方便用户在不同目录之间切换,Bash 提供了目录堆栈功能。
4 |
5 | ## cd -
6 |
7 | Bash 可以记忆用户进入过的目录。默认情况下,只记忆前一次所在的目录,`cd -`命令可以返回前一次的目录。
8 |
9 | ```bash
10 | # 当前目录是 /path/to/foo
11 | $ cd bar
12 |
13 | # 重新回到 /path/to/foo
14 | $ cd -
15 | ```
16 |
17 | 上面例子中,用户原来所在的目录是`/path/to/foo`,进入子目录`bar`以后,使用`cd -`可以回到原来的目录。
18 |
19 | ## pushd,popd
20 |
21 | 如果希望记忆多重目录,可以使用`pushd`命令和`popd`命令。它们用来操作目录堆栈。
22 |
23 | `pushd`命令的用法类似`cd`命令,可以进入指定的目录。
24 |
25 | ```bash
26 | $ pushd dirname
27 | ```
28 |
29 | 上面命令会进入目录`dirname`,并将该目录放入堆栈。
30 |
31 | 第一次使用`pushd`命令时,会将当前目录先放入堆栈,然后将所要进入的目录也放入堆栈,位置在前一个记录的上方。以后每次使用`pushd`命令,都会将所要进入的目录,放在堆栈的顶部。
32 |
33 | `popd`命令不带有参数时,会移除堆栈的顶部记录,并进入新的栈顶目录(即原来的第二条目录)。
34 |
35 | 下面是一个例子。
36 |
37 | ```bash
38 | # 当前处在主目录,堆栈为空
39 | $ pwd
40 | /home/me
41 |
42 | # 进入 /home/me/foo
43 | # 当前堆栈为 /home/me/foo /home/me
44 | $ pushd ~/foo
45 |
46 | # 进入 /etc
47 | # 当前堆栈为 /etc /home/me/foo /home/me
48 | $ pushd /etc
49 |
50 | # 进入 /home/me/foo
51 | # 当前堆栈为 /home/me/foo /home/me
52 | $ popd
53 |
54 | # 进入 /home/me
55 | # 当前堆栈为 /home/me
56 | $ popd
57 |
58 | # 目录不变,当前堆栈为空
59 | $ popd
60 | ```
61 |
62 | 这两个命令的参数如下。
63 |
64 | **(1)-n 参数**
65 |
66 | `-n`的参数表示仅操作堆栈,不改变目录。
67 |
68 | ```bash
69 | $ popd -n
70 | ```
71 |
72 | 上面的命令仅删除堆栈顶部的记录,不改变目录,执行完成后还停留在当前目录。
73 |
74 | **(2)整数参数**
75 |
76 | 这两个命令还可以接受一个整数作为参数,该整数表示堆栈中指定位置的记录(从0开始)。`pushd`命令会把这条记录移动到栈顶,同时切换到该目录;`popd`则从堆栈中删除这条记录,不会切换目录。
77 |
78 | ```bash
79 | # 将从栈顶算起的3号目录(从0开始)移动到栈顶,同时切换到该目录
80 | $ pushd +3
81 |
82 | # 将从栈底算起的3号目录(从0开始)移动到栈顶,同时切换到该目录
83 | $ pushd -3
84 |
85 | # 删除从栈顶算起的3号目录(从0开始),不改变当前目录
86 | $ popd +3
87 |
88 | # 删除从栈底算起的3号目录(从0开始),不改变当前目录
89 | $ popd -3
90 | ```
91 |
92 | 上面例子的整数编号都是从0开始计算,`popd +0`是删除第一个目录,`popd +1`是删除第二个,`popd -0`是删除最后一个目录,`popd -1`是删除倒数第二个。
93 |
94 | **(3)目录参数**
95 |
96 | `pushd`可以接受一个目录作为参数,表示将该目录放到堆栈顶部,并进入该目录。
97 |
98 | ```bash
99 | $ pushd dir
100 | ```
101 |
102 | `popd`没有这个参数。
103 |
104 | ## dirs 命令
105 |
106 | `dirs`命令可以显示目录堆栈的内容,一般用来查看`pushd`和`popd`操作后的结果。
107 |
108 | ```bash
109 | $ dirs
110 | ~/foo/bar ~/foo ~
111 | ```
112 |
113 | 该命令会输出一行文本,列出目录堆栈,目录之间使用空格分隔。栈顶(最晚入栈的目录)在最左边,栈底(最早入栈的目录)在最右边。
114 |
115 | 它有以下参数。
116 |
117 | - `-c`:清空目录栈。
118 | - `-l`:用户主目录不显示波浪号前缀,而打印完整的目录。
119 | - `-p`:每行一个条目打印目录栈,默认是打印在一行。
120 | - `-v`:每行一个条目,每个条目之前显示位置编号(从0开始)。
121 | - `+N`:`N`为整数,表示显示堆顶算起的第 N 个目录,从零开始。
122 | - `-N`:`N`为整数,表示显示堆底算起的第 N 个目录,从零开始。
123 |
124 |
--------------------------------------------------------------------------------
/docs/startup.md:
--------------------------------------------------------------------------------
1 | # Bash 启动环境
2 |
3 | ## Session
4 |
5 | 用户每次使用 Shell,都会开启一个与 Shell 的 Session(对话)。
6 |
7 | Session 有两种类型:登录 Session 和非登录 Session,也可以叫做 login shell 和 non-login shell。
8 |
9 | ### 登录 Session
10 |
11 | 登录 Session 是用户登录系统以后,系统为用户开启的原始 Session,通常需要用户输入用户名和密码进行登录。
12 |
13 | 登录 Session 一般进行整个系统环境的初始化,启动的初始化脚本依次如下。
14 |
15 | - `/etc/profile`:所有用户的全局配置脚本。
16 | - `/etc/profile.d`目录里面所有`.sh`文件
17 | - `~/.bash_profile`:用户的个人配置脚本。如果该脚本存在,则执行完就不再往下执行。
18 | - `~/.bash_login`:如果`~/.bash_profile`没找到,则尝试执行这个脚本(C shell 的初始化脚本)。如果该脚本存在,则执行完就不再往下执行。
19 | - `~/.profile`:如果`~/.bash_profile`和`~/.bash_login`都没找到,则尝试读取这个脚本(Bourne shell 和 Korn shell 的初始化脚本)。
20 |
21 | Linux 发行版更新的时候,会更新`/etc`里面的文件,比如`/etc/profile`,因此不要直接修改这个文件。如果想修改所有用户的登陆环境,就在`/etc/profile.d`目录里面新建`.sh`脚本。
22 |
23 | 如果想修改你个人的登录环境,一般是写在`~/.bash_profile`里面。下面是一个典型的`.bash_profile`文件。
24 |
25 | ```bash
26 | # .bash_profile
27 | PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
28 | PATH=$PATH:$HOME/bin
29 |
30 | SHELL=/bin/bash
31 | MANPATH=/usr/man:/usr/X11/man
32 | EDITOR=/usr/bin/vi
33 | PS1='\h:\w\$ '
34 | PS2='> '
35 |
36 | if [ -f ~/.bashrc ]; then
37 | . ~/.bashrc
38 | fi
39 |
40 | export PATH
41 | export EDITOR
42 | ```
43 |
44 | 可以看到,这个脚本定义了一些最基本的环境变量,然后执行了`~/.bashrc`。
45 |
46 | `bash`命令的`--login`参数,会强制执行登录 Session 会执行的脚本。
47 |
48 | ```bash
49 | $ bash --login
50 | ```
51 |
52 | `bash`命令的`--noprofile`参数,会跳过上面这些 Profile 脚本。
53 |
54 | ```bash
55 | $ bash --noprofile
56 | ```
57 |
58 | ### 非登录 Session
59 |
60 | 非登录 Session 是用户进入系统以后,手动新建的 Session,这时不会进行环境初始化。比如,在命令行执行`bash`命令,就会新建一个非登录 Session。
61 |
62 | 非登录 Session 的初始化脚本依次如下。
63 |
64 | - `/etc/bash.bashrc`:对全体用户有效。
65 | - `~/.bashrc`:仅对当前用户有效。
66 |
67 | 对用户来说,`~/.bashrc`通常是最重要的脚本。非登录 Session 默认会执行它,而登录 Session 一般也会通过调用执行它。每次新建一个 Bash 窗口,就相当于新建一个非登录 Session,所以`~/.bashrc`每次都会执行。注意,执行脚本相当于新建一个非互动的 Bash 环境,但是这种情况不会调用`~/.bashrc`。
68 |
69 | `bash`命令的`--norc`参数,可以禁止在非登录 Session 执行`~/.bashrc`脚本。
70 |
71 | ```bash
72 | $ bash --norc
73 | ```
74 |
75 | `bash`命令的`--rcfile`参数,指定另一个脚本代替`.bashrc`。
76 |
77 | ```bash
78 | $ bash --rcfile testrc
79 | ```
80 |
81 | ### .bash_logout
82 |
83 | `~/.bash_logout`脚本在每次退出 Session 时执行,通常用来做一些清理工作和记录工作,比如删除临时文件,记录用户在本次 Session 花费的时间。
84 |
85 | 如果没有退出时要执行的命令,这个文件也可以不存在。
86 |
87 | ## 启动选项
88 |
89 | 为了方便 Debug,有时在启动 Bash 的时候,可以加上启动参数。
90 |
91 | - `-n`:不运行脚本,只检查是否有语法错误。
92 | - `-v`:输出每一行语句运行结果前,会先输出该行语句。
93 | - `-x`:每一个命令处理之前,先输出该命令,再执行该命令。
94 |
95 | ```bash
96 | $ bash -n scriptname
97 | $ bash -v scriptname
98 | $ bash -x scriptname
99 | ```
100 |
101 | ## 键盘绑定
102 |
103 | Bash 允许用户定义自己的快捷键。全局的键盘绑定文件默认为`/etc/inputrc`,你可以在主目录创建自己的键盘绑定文件`.inputrc`文件。如果定义了这个文件,需要在其中加入下面这行,保证全局绑定不会被遗漏。
104 |
105 | ```bash
106 | $include /etc/inputrc
107 | ```
108 |
109 | `.inputrc`文件里面的快捷键,可以像这样定义,`"\C-t":"pwd\n"`表示将`Ctrl + t`绑定为运行`pwd`命令。
110 |
111 |
--------------------------------------------------------------------------------
/docs/string.md:
--------------------------------------------------------------------------------
1 | # 字符串操作
2 |
3 | 本章介绍 Bash 字符串操作的语法。
4 |
5 | ## 字符串的长度
6 |
7 | 获取字符串长度的语法如下。
8 |
9 | ```bash
10 | ${#varname}
11 | ```
12 |
13 | 下面是一个例子。
14 |
15 | ```bash
16 | $ myPath=/home/cam/book/long.file.name
17 | $ echo ${#myPath}
18 | 29
19 | ```
20 |
21 | 大括号`{}`是必需的,否则 Bash 会将`$#`理解成脚本的参数个数,将变量名理解成文本。
22 |
23 | ```bash
24 | $ echo $#myvar
25 | 0myvar
26 | ```
27 |
28 | 上面例子中,Bash 将`$#`和`myvar`分开解释了。
29 |
30 | ## 子字符串
31 |
32 | 字符串提取子串的语法如下。
33 |
34 | ```bash
35 | ${varname:offset:length}
36 | ```
37 |
38 | 上面语法的含义是返回变量`$varname`的子字符串,从位置`offset`开始(从`0`开始计算),长度为`length`。
39 |
40 | ```bash
41 | $ count=frogfootman
42 | $ echo ${count:4:4}
43 | foot
44 | ```
45 |
46 | 上面例子返回字符串`frogfootman`从4号位置开始的长度为4的子字符串`foot`。
47 |
48 | 这种语法不能直接操作字符串,只能通过变量来读取字符串,并且不会改变原始字符串。
49 |
50 | ```bash
51 | # 报错
52 | $ echo ${"hello":2:3}
53 | ```
54 |
55 | 上面例子中,`"hello"`不是变量名,导致 Bash 报错。
56 |
57 | 如果省略`length`,则从位置`offset`开始,一直返回到字符串的结尾。
58 |
59 | ```bash
60 | $ count=frogfootman
61 | $ echo ${count:4}
62 | footman
63 | ```
64 |
65 | 上面例子是返回变量`count`从4号位置一直到结尾的子字符串。
66 |
67 | 如果`offset`为负值,表示从字符串的末尾开始算起。注意,负数前面必须有一个空格, 以防止与`${variable:-word}`的变量的设置默认值语法混淆。这时还可以指定`length`,`length`可以是正值,也可以是负值(负值不能超过`offset`的长度)。
68 |
69 | ```bash
70 | $ foo="This string is long."
71 | $ echo ${foo: -5}
72 | long.
73 | $ echo ${foo: -5:2}
74 | lo
75 | $ echo ${foo: -5:-2}
76 | lon
77 | ```
78 |
79 | 上面例子中,`offset`为`-5`,表示从倒数第5个字符开始截取,所以返回`long.`。如果指定长度`length`为`2`,则返回`lo`;如果`length`为`-2`,表示要排除从字符串末尾开始的2个字符,所以返回`lon`。
80 |
81 | ## 搜索和替换
82 |
83 | Bash 提供字符串搜索和替换的多种方法。
84 |
85 | **(1)字符串头部的模式匹配。**
86 |
87 | 以下两种语法可以检查字符串开头,是否匹配给定的模式。如果匹配成功,就删除匹配的部分,返回剩下的部分。原始变量不会发生变化。
88 |
89 | ```bash
90 | # 如果 pattern 匹配变量 variable 的开头,
91 | # 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
92 | ${variable#pattern}
93 |
94 | # 如果 pattern 匹配变量 variable 的开头,
95 | # 删除最长匹配(贪婪匹配)的部分,返回剩余部分
96 | ${variable##pattern}
97 | ```
98 |
99 | 上面两种语法会删除变量字符串开头的匹配部分(将其替换为空),返回剩下的部分。区别是一个是最短匹配(又称非贪婪匹配),另一个是最长匹配(又称贪婪匹配)。
100 |
101 | 匹配模式`pattern`可以使用`*`、`?`、`[]`等通配符。
102 |
103 | ```bash
104 | $ myPath=/home/cam/book/long.file.name
105 |
106 | $ echo ${myPath#/*/}
107 | cam/book/long.file.name
108 |
109 | $ echo ${myPath##/*/}
110 | long.file.name
111 | ```
112 |
113 | 上面例子中,匹配的模式是`/*/`,其中`*`可以匹配任意数量的字符,所以最短匹配是`/home/`,最长匹配是`/home/cam/book/`。
114 |
115 | 下面写法可以删除文件路径的目录部分,只留下文件名。
116 |
117 | ```bash
118 | $ path=/home/cam/book/long.file.name
119 |
120 | $ echo ${path##*/}
121 | long.file.name
122 | ```
123 |
124 | 上面例子中,模式`*/`匹配目录部分,所以只返回文件名。
125 |
126 | 下面再看一个例子。
127 |
128 | ```bash
129 | $ phone="555-456-1414"
130 | $ echo ${phone#*-}
131 | 456-1414
132 | $ echo ${phone##*-}
133 | 1414
134 | ```
135 |
136 | 如果匹配不成功,则返回原始字符串。
137 |
138 | ```bash
139 | $ phone="555-456-1414"
140 | $ echo ${phone#444}
141 | 555-456-1414
142 | ```
143 |
144 | 上面例子中,原始字符串里面无法匹配模式`444`,所以原样返回。
145 |
146 | 如果要将头部匹配的部分,替换成其他内容,采用下面的写法。
147 |
148 | ```bash
149 | # 模式必须出现在字符串的开头
150 | ${variable/#pattern/string}
151 |
152 | # 示例
153 | $ foo=JPG.JPG
154 | $ echo ${foo/#JPG/jpg}
155 | jpg.JPG
156 | ```
157 |
158 | 上面例子中,被替换的`JPG`必须出现在字符串头部,所以返回`jpg.JPG`。
159 |
160 | **(2)字符串尾部的模式匹配。**
161 |
162 | 以下两种语法可以检查字符串结尾,是否匹配给定的模式。如果匹配成功,就删除匹配的部分,返回剩下的部分。原始变量不会发生变化。
163 |
164 | ```bash
165 | # 如果 pattern 匹配变量 variable 的结尾,
166 | # 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
167 | ${variable%pattern}
168 |
169 | # 如果 pattern 匹配变量 variable 的结尾,
170 | # 删除最长匹配(贪婪匹配)的部分,返回剩余部分
171 | ${variable%%pattern}
172 | ```
173 |
174 | 上面两种语法会删除变量字符串结尾的匹配部分(将其替换为空),返回剩下的部分。区别是一个是最短匹配(又称非贪婪匹配),另一个是最长匹配(又称贪婪匹配)。
175 |
176 | ```bash
177 | $ path=/home/cam/book/long.file.name
178 |
179 | $ echo ${path%.*}
180 | /home/cam/book/long.file
181 |
182 | $ echo ${path%%.*}
183 | /home/cam/book/long
184 | ```
185 |
186 | 上面例子中,匹配模式是`.*`,其中`*`可以匹配任意数量的字符,所以最短匹配是`.name`,最长匹配是`.file.name`。
187 |
188 | 下面写法可以删除路径的文件名部分,只留下目录部分。
189 |
190 | ```bash
191 | $ path=/home/cam/book/long.file.name
192 |
193 | $ echo ${path%/*}
194 | /home/cam/book
195 | ```
196 |
197 | 上面例子中,模式`/*`匹配文件名部分,所以只返回目录部分。
198 |
199 | 下面的写法可以替换文件的后缀名。
200 |
201 | ```bash
202 | $ file=foo.png
203 | $ echo ${file%.png}.jpg
204 | foo.jpg
205 | ```
206 |
207 | 上面的例子将文件的后缀名,从`.png`改成了`.jpg`。
208 |
209 | 下面再看一个例子。
210 |
211 | ```bash
212 | $ phone="555-456-1414"
213 | $ echo ${phone%-*}
214 | 555-456
215 | $ echo ${phone%%-*}
216 | 555
217 | ```
218 |
219 | 如果匹配不成功,则返回原始字符串。
220 |
221 | 如果要将尾部匹配的部分,替换成其他内容,采用下面的写法。
222 |
223 | ```bash
224 | # 模式必须出现在字符串的结尾
225 | ${variable/%pattern/string}
226 |
227 | # 示例
228 | $ foo=JPG.JPG
229 | $ echo ${foo/%JPG/jpg}
230 | JPG.jpg
231 | ```
232 |
233 | 上面例子中,被替换的`JPG`必须出现在字符串尾部,所以返回`JPG.jpg`。
234 |
235 | **(3)任意位置的模式匹配。**
236 |
237 | 以下两种语法可以检查字符串内部,是否匹配给定的模式。如果匹配成功,就删除匹配的部分,换成其他的字符串返回。原始变量不会发生变化。
238 |
239 | ```bash
240 | # 如果 pattern 匹配变量 variable 的一部分,
241 | # 最长匹配(贪婪匹配)的那部分被 string 替换,但仅替换第一个匹配
242 | ${variable/pattern/string}
243 |
244 | # 如果 pattern 匹配变量 variable 的一部分,
245 | # 最长匹配(贪婪匹配)的那部分被 string 替换,所有匹配都替换
246 | ${variable//pattern/string}
247 | ```
248 |
249 | 上面两种语法都是最长匹配(贪婪匹配)下的替换,区别是前一个语法仅仅替换第一个匹配,后一个语法替换所有匹配。
250 |
251 | ```bash
252 | $ path=/home/cam/foo/foo.name
253 |
254 | $ echo ${path/foo/bar}
255 | /home/cam/bar/foo.name
256 |
257 | $ echo ${path//foo/bar}
258 | /home/cam/bar/bar.name
259 | ```
260 |
261 | 上面例子中,前一个命令只替换了第一个`foo`,后一个命令将两个`foo`都替换了。
262 |
263 | 下面的例子将分隔符从`:`换成换行符。
264 |
265 | ```bash
266 | $ echo -e ${PATH//:/'\n'}
267 | /usr/local/bin
268 | /usr/bin
269 | /bin
270 | ...
271 | ```
272 |
273 | 上面例子中,`echo`命令的`-e`参数,表示将替换后的字符串的`\n`字符,解释为换行符。
274 |
275 | 模式部分可以使用通配符。
276 |
277 | ```bash
278 | $ phone="555-456-1414"
279 | $ echo ${phone/5?4/-}
280 | 55-56-1414
281 | ```
282 |
283 | 上面的例子将`5-4`替换成`-`。
284 |
285 | 如果省略了`string`部分,那么就相当于匹配的部分替换成空字符串,即删除匹配的部分。
286 |
287 | ```bash
288 | $ path=/home/cam/foo/foo.name
289 |
290 | $ echo ${path/.*/}
291 | /home/cam/foo/foo
292 | ```
293 |
294 | 上面例子中,第二个斜杠后面的`string`部分省略了,所以模式`.*`匹配的部分`.name`被删除后返回。
295 |
296 | 前面提到过,这个语法还有两种扩展形式。
297 |
298 | ```bash
299 | # 模式必须出现在字符串的开头
300 | ${variable/#pattern/string}
301 |
302 | # 模式必须出现在字符串的结尾
303 | ${variable/%pattern/string}
304 | ```
305 |
306 | ## 改变大小写
307 |
308 | 下面的语法可以改变变量的大小写。
309 |
310 | ```bash
311 | # 转为大写
312 | ${varname^^}
313 |
314 | # 转为小写
315 | ${varname,,}
316 | ```
317 |
318 | 下面是一个例子。
319 |
320 | ```bash
321 | $ foo=heLLo
322 | $ echo ${foo^^}
323 | HELLO
324 | $ echo ${foo,,}
325 | hello
326 | ```
327 |
328 |
--------------------------------------------------------------------------------
/docs/variable.md:
--------------------------------------------------------------------------------
1 | # Bash 变量
2 |
3 | ## 简介
4 |
5 | Bash 变量分成环境变量和自定义变量两类。
6 |
7 | ### 环境变量
8 |
9 | 环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。
10 |
11 | `env`命令或`printenv`命令,可以显示所有环境变量。
12 |
13 | ```bash
14 | $ env
15 | # 或者
16 | $ printenv
17 | ```
18 |
19 | 下面是一些常见的环境变量。
20 |
21 | - `BASHPID`:Bash 进程的进程 ID。
22 | - `BASHOPTS`:当前 Shell 的参数,可以用`shopt`命令修改。
23 | - `DISPLAY`:图形环境的显示器名字,通常是`:0`,表示 X Server 的第一个显示器。
24 | - `EDITOR`:默认的文本编辑器。
25 | - `HOME`:用户的主目录。
26 | - `HOST`:当前主机的名称。
27 | - `IFS`:词与词之间的分隔符,默认为空格。
28 | - `LANG`:字符集以及语言编码,比如`zh_CN.UTF-8`。
29 | - `PATH`:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。
30 | - `PS1`:Shell 提示符。
31 | - `PS2`: 输入多行命令时,次要的 Shell 提示符。
32 | - `PWD`:当前工作目录。
33 | - `RANDOM`:返回一个0到32767之间的随机数。
34 | - `SHELL`:Shell 的名字。
35 | - `SHELLOPTS`:启动当前 Shell 的`set`命令的参数,参见《set 命令》一章。
36 | - `TERM`:终端类型名,即终端仿真器所用的协议。
37 | - `UID`:当前用户的 ID 编号。
38 | - `USER`:当前用户的用户名。
39 |
40 | 很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。
41 |
42 | 注意,Bash 变量名区分大小写,`HOME`和`home`是两个不同的变量。
43 |
44 | 查看单个环境变量的值,可以使用`printenv`命令或`echo`命令。
45 |
46 | ```bash
47 | $ printenv PATH
48 | # 或者
49 | $ echo $PATH
50 | ```
51 |
52 | 注意,`printenv`命令后面的变量名,不用加前缀`$`。
53 |
54 | ### 自定义变量
55 |
56 | 自定义变量是用户在当前 Shell 里面自己定义的变量,仅在当前 Shell 可用。一旦退出当前 Shell,该变量就不存在了。
57 |
58 | `set`命令可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数。
59 |
60 | ```bash
61 | $ set
62 | ```
63 |
64 | ## 创建变量
65 |
66 | 用户创建变量的时候,变量名必须遵守下面的规则。
67 |
68 | - 字母、数字和下划线字符组成。
69 | - 第一个字符必须是一个字母或一个下划线,不能是数字。
70 | - 不允许出现空格和标点符号。
71 |
72 | 变量声明的语法如下。
73 |
74 | ```bash
75 | variable=value
76 | ```
77 |
78 | 上面命令中,等号左边是变量名,右边是变量。注意,等号两边不能有空格。
79 |
80 | 如果变量的值包含空格,则必须将值放在引号中。
81 |
82 | ```bash
83 | myvar="hello world"
84 | ```
85 |
86 | Bash 没有数据类型的概念,所有的变量值都是字符串。
87 |
88 | 下面是一些自定义变量的例子。
89 |
90 | ```bash
91 | a=z # 变量 a 赋值为字符串 z
92 | b="a string" # 变量值包含空格,就必须放在引号里面
93 | c="a string and $b" # 变量值可以引用其他变量的值
94 | d="\t\ta string\n" # 变量值可以使用转义字符
95 | e=$(ls -l foo.txt) # 变量值可以是命令的执行结果
96 | f=$((5 * 7)) # 变量值可以是数学运算的结果
97 | ```
98 |
99 | 变量可以重复赋值,后面的赋值会覆盖前面的赋值。
100 |
101 | ```bash
102 | $ foo=1
103 | $ foo=2
104 | $ echo $foo
105 | 2
106 | ```
107 |
108 | 上面例子中,变量`foo`的第二次赋值会覆盖第一次赋值。
109 |
110 | 如果同一行定义多个变量,必须使用分号(`;`)分隔。
111 |
112 | ```bash
113 | $ foo=1;bar=2
114 | ```
115 |
116 | 上面例子中,同一行定义了`foo`和`bar`两个变量。
117 |
118 | ## 读取变量
119 |
120 | 读取变量的时候,直接在变量名前加上`$`就可以了。
121 |
122 | ```bash
123 | $ foo=bar
124 | $ echo $foo
125 | bar
126 | ```
127 |
128 | 每当 Shell 看到以`$`开头的单词时,就会尝试读取这个变量名对应的值。
129 |
130 | 如果变量不存在,Bash 不会报错,而会输出空字符。
131 |
132 | 由于`$`在 Bash 中有特殊含义,把它当作美元符号使用时,一定要非常小心,
133 |
134 | ```bash
135 | $ echo The total is $100.00
136 | The total is 00.00
137 | ```
138 |
139 | 上面命令的原意是输入`$100`,但是 Bash 将`$1`解释成了变量,该变量为空,因此输入就变成了`00.00`。所以,如果要使用`$`的原义,需要在`$`前面放上反斜杠,进行转义。
140 |
141 | ```bash
142 | $ echo The total is \$100.00
143 | The total is $100.00
144 | ```
145 |
146 | 读取变量的时候,变量名也可以使用花括号`{}`包围,比如`$a`也可以写成`${a}`。这种写法可以用于变量名与其他字符连用的情况。
147 |
148 | ```bash
149 | $ a=foo
150 | $ echo $a_file
151 |
152 | $ echo ${a}_file
153 | foo_file
154 | ```
155 |
156 | 上面代码中,变量名`a_file`不会有任何输出,因为 Bash 将其整个解释为变量,而这个变量是不存在的。只有用花括号区分`$a`,Bash 才能正确解读。
157 |
158 | 事实上,读取变量的语法`$foo`,可以看作是`${foo}`的简写形式。
159 |
160 | 如果变量的值本身也是变量,可以使用`${!varname}`的语法,读取最终的值。
161 |
162 | ```bash
163 | $ myvar=USER
164 | $ echo ${!myvar}
165 | ruanyf
166 | ```
167 |
168 | 上面的例子中,变量`myvar`的值是`USER`,`${!myvar}`的写法将其展开成最终的值。
169 |
170 | 如果变量值包含连续空格(或制表符和换行符),最好放在双引号里面读取。
171 |
172 | ```bash
173 | $ a="1 2 3"
174 | $ echo $a
175 | 1 2 3
176 | $ echo "$a"
177 | 1 2 3
178 | ```
179 |
180 | 上面示例中,变量`a`的值包含两个连续空格。如果直接读取,Shell 会将连续空格合并成一个。只有放在双引号里面读取,才能保持原来的格式。
181 |
182 | ## 删除变量
183 |
184 | `unset`命令用来删除一个变量。
185 |
186 | ```bash
187 | unset NAME
188 | ```
189 |
190 | 这个命令不是很有用。因为不存在的 Bash 变量一律等于空字符串,所以即使`unset`命令删除了变量,还是可以读取这个变量,值为空字符串。
191 |
192 | 所以,删除一个变量,也可以将这个变量设成空字符串。
193 |
194 | ```bash
195 | $ foo=''
196 | $ foo=
197 | ```
198 |
199 | 上面两种写法,都是删除了变量`foo`。由于不存在的值默认为空字符串,所以后一种写法可以在等号右边不写任何值。
200 |
201 | ## 输出变量,export 命令
202 |
203 | 用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用`export`命令。这样输出的变量,对于子 Shell 来说就是环境变量。
204 |
205 | `export`命令用来向子 Shell 输出变量。
206 |
207 | ```bash
208 | NAME=foo
209 | export NAME
210 | ```
211 |
212 | 上面命令输出了变量`NAME`。变量的赋值和输出也可以在一个步骤中完成。
213 |
214 | ```bash
215 | export NAME=value
216 | ```
217 |
218 | 上面命令执行后,当前 Shell 及随后新建的子 Shell,都可以读取变量`$NAME`。
219 |
220 | 子 Shell 如果修改继承的变量,不会影响父 Shell。
221 |
222 | ```bash
223 | # 输出变量 $foo
224 | $ export foo=bar
225 |
226 | # 新建子 Shell
227 | $ bash
228 |
229 | # 读取 $foo
230 | $ echo $foo
231 | bar
232 |
233 | # 修改继承的变量
234 | $ foo=baz
235 |
236 | # 退出子 Shell
237 | $ exit
238 |
239 | # 读取 $foo
240 | $ echo $foo
241 | bar
242 | ```
243 |
244 | 上面例子中,子 Shell 修改了继承的变量`$foo`,对父 Shell 没有影响。
245 |
246 | ## 特殊变量
247 |
248 | Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。
249 |
250 | (1)`$?`
251 |
252 | `$?`为上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是`0`,表示上一个命令执行成功;如果不是零,表示上一个命令执行失败。
253 |
254 | ```bash
255 | $ ls doesnotexist
256 | ls: doesnotexist: No such file or directory
257 |
258 | $ echo $?
259 | 1
260 | ```
261 |
262 | 上面例子中,`ls`命令查看一个不存在的文件,导致报错。`$?`为1,表示上一个命令执行失败。
263 |
264 | (2)`$$`
265 |
266 | `$$`为当前 Shell 的进程 ID。
267 |
268 | ```bash
269 | $ echo $$
270 | 10662
271 | ```
272 |
273 | 这个特殊变量可以用来命名临时文件。
274 |
275 | ```bash
276 | LOGFILE=/tmp/output_log.$$
277 | ```
278 |
279 | (3)`$_`
280 |
281 | `$_`为上一个命令的最后一个参数。
282 |
283 | ```bash
284 | $ grep dictionary /usr/share/dict/words
285 | dictionary
286 |
287 | $ echo $_
288 | /usr/share/dict/words
289 | ```
290 |
291 | (4)`$!`
292 |
293 | `$!`为最近一个后台执行的异步命令的进程 ID。
294 |
295 | ```bash
296 | $ firefox &
297 | [1] 11064
298 |
299 | $ echo $!
300 | 11064
301 | ```
302 |
303 | 上面例子中,`firefox`是后台运行的命令,`$!`返回该命令的进程 ID。
304 |
305 | (5)`$0`
306 |
307 | `$0`为当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时)。
308 |
309 | ```bash
310 | $ echo $0
311 | bash
312 | ```
313 |
314 | 上面例子中,`$0`返回当前运行的是 Bash。
315 |
316 | (6)`$-`
317 |
318 | `$-`为当前 Shell 的启动参数。
319 |
320 | ```bash
321 | $ echo $-
322 | himBHs
323 | ```
324 |
325 | (7)`$@`和`$#`
326 |
327 | `$#`表示脚本的参数数量,`$@`表示脚本的参数值,参见脚本一章。
328 |
329 | ## 变量的默认值
330 |
331 | Bash 提供四个特殊语法,跟变量的默认值有关,目的是保证变量不为空。
332 |
333 | ```bash
334 | ${varname:-word}
335 | ```
336 |
337 | 上面语法的含义是,如果变量`varname`存在且不为空,则返回它的值,否则返回`word`。它的目的是返回一个默认值,比如`${count:-0}`表示变量`count`不存在时返回`0`。
338 |
339 |
340 | ```bash
341 | ${varname:=word}
342 | ```
343 |
344 | 上面语法的含义是,如果变量`varname`存在且不为空,则返回它的值,否则将它设为`word`,并且返回`word`。它的目的是设置变量的默认值,比如`${count:=0}`表示变量`count`不存在时返回`0`,且将`count`设为`0`。
345 |
346 | ```bash
347 | ${varname:+word}
348 | ```
349 |
350 | 上面语法的含义是,如果变量名存在且不为空,则返回`word`,否则返回空值。它的目的是测试变量是否存在,比如`${count:+1}`表示变量`count`存在时返回`1`(表示`true`),否则返回空值。
351 |
352 | ```bash
353 | ${varname:?message}
354 | ```
355 |
356 | 上面语法的含义是,如果变量`varname`存在且不为空,则返回它的值,否则打印出`varname: message`,并中断脚本的执行。如果省略了`message`,则输出默认的信息“parameter null or not set.”。它的目的是防止变量未定义,比如`${count:?"undefined!"}`表示变量`count`未定义时就中断执行,抛出错误,返回给定的报错信息`undefined!`。
357 |
358 | 上面四种语法如果用在脚本中,变量名的部分可以用数字`1`到`9`,表示脚本的参数。
359 |
360 | ```bash
361 | filename=${1:?"filename missing."}
362 | ```
363 |
364 | 上面代码出现在脚本中,`1`表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。
365 |
366 | ## declare 命令
367 |
368 | `declare`命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。
369 |
370 | 它的语法形式如下。
371 |
372 | ```bash
373 | declare OPTION VARIABLE=value
374 | ```
375 |
376 | `declare`命令的主要参数(OPTION)如下。
377 |
378 | - `-a`:声明数组变量。
379 | - `-f`:输出所有函数定义。
380 | - `-F`:输出所有函数名。
381 | - `-i`:声明整数变量。
382 | - `-l`:声明变量为小写字母。
383 | - `-p`:查看变量信息。
384 | - `-r`:声明只读变量。
385 | - `-u`:声明变量为大写字母。
386 | - `-x`:该变量输出为环境变量。
387 |
388 | `declare`命令如果用在函数中,声明的变量只在函数内部有效,等同于`local`命令。
389 |
390 | 不带任何参数时,`declare`命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的`set`命令。
391 |
392 | ```bash
393 | $ declare
394 | ```
395 |
396 | **(1)`-i`参数**
397 |
398 | `-i`参数声明整数变量以后,可以直接进行数学运算。
399 |
400 | ```bash
401 | $ declare -i val1=12 val2=5
402 | $ declare -i result
403 | $ result=val1*val2
404 | $ echo $result
405 | 60
406 | ```
407 |
408 | 上面例子中,如果变量`result`不声明为整数,`val1*val2`会被当作字面量,不会进行整数运算。另外,`val1`和`val2`其实不需要声明为整数,因为只要`result`声明为整数,它的赋值就会自动解释为整数运算。
409 |
410 | 注意,一个变量声明为整数以后,依然可以被改写为字符串。
411 |
412 | ```bash
413 | $ declare -i var=12
414 | $ var=foo
415 | $ echo $var
416 | 0
417 | ```
418 |
419 | 上面例子中,变量`var`声明为整数,覆盖以后,Bash 不会报错,但会赋以不确定的值,上面的例子中可能输出0,也可能输出的是3。
420 |
421 | **(2)`-x`参数**
422 |
423 | `-x`参数等同于`export`命令,可以输出一个变量为子 Shell 的环境变量。
424 |
425 | ```bash
426 | $ declare -x foo
427 | # 等同于
428 | $ export foo
429 | ```
430 |
431 | **(3)`-r`参数**
432 |
433 | `-r`参数可以声明只读变量,无法改变变量值,也不能`unset`变量。
434 |
435 | ```bash
436 | $ declare -r bar=1
437 |
438 | $ bar=2
439 | bash: bar:只读变量
440 | $ echo $?
441 | 1
442 |
443 | $ unset bar
444 | bash: bar:只读变量
445 | $ echo $?
446 | 1
447 | ```
448 |
449 | 上面例子中,后两个赋值语句都会报错,命令执行失败。
450 |
451 | **(4)`-u`参数**
452 |
453 | `-u`参数声明变量为大写字母,可以自动把变量值转成大写字母。
454 |
455 | ```bash
456 | $ declare -u foo
457 | $ foo=upper
458 | $ echo $foo
459 | UPPER
460 | ```
461 |
462 | **(5)`-l`参数**
463 |
464 | `-l`参数声明变量为小写字母,可以自动把变量值转成小写字母。
465 |
466 | ```bash
467 | $ declare -l bar
468 | $ bar=LOWER
469 | $ echo $bar
470 | lower
471 | ```
472 |
473 | **(6)`-p`参数**
474 |
475 | `-p`参数输出变量信息。
476 |
477 | ```bash
478 | $ foo=hello
479 | $ declare -p foo
480 | declare -- foo="hello"
481 | $ declare -p bar
482 | bar:未找到
483 | ```
484 |
485 | 上面例子中,`declare -p`可以输出已定义变量的值,对于未定义的变量,会提示找不到。
486 |
487 | 如果不提供变量名,`declare -p`输出所有变量的信息。
488 |
489 | ```bash
490 | $ declare -p
491 | ```
492 |
493 | **(7)`-f`参数**
494 |
495 | `-f`参数输出当前环境的所有函数,包括它的定义。
496 |
497 | ```bash
498 | $ declare -f
499 | ```
500 |
501 | **(8)`-F`参数**
502 |
503 | `-F`参数输出当前环境的所有函数名,不包含函数定义。
504 |
505 | ```bash
506 | $ declare -F
507 | ```
508 |
509 | ## readonly 命令
510 |
511 | `readonly`命令等同于`declare -r`,用来声明只读变量,不能改变变量值,也不能`unset`变量。
512 |
513 | ```bash
514 | $ readonly foo=1
515 | $ foo=2
516 | bash: foo:只读变量
517 | $ echo $?
518 | 1
519 | ```
520 |
521 | 上面例子中,更改只读变量`foo`会报错,命令执行失败。
522 |
523 | `readonly`命令有三个参数。
524 |
525 | - `-f`:声明的变量为函数名。
526 | - `-p`:打印出所有的只读变量。
527 | - `-a`:声明的变量为数组。
528 |
529 | ## let 命令
530 |
531 | `let`命令声明变量时,可以直接执行算术表达式。
532 |
533 | ```bash
534 | $ let foo=1+2
535 | $ echo $foo
536 | 3
537 | ```
538 |
539 | 上面例子中,`let`命令可以直接计算`1 + 2`。
540 |
541 | `let`命令的参数表达式如果包含空格,就需要使用引号。
542 |
543 | ```bash
544 | $ let "foo = 1 + 2"
545 | ```
546 |
547 | `let`可以同时对多个变量赋值,赋值表达式之间使用空格分隔。
548 |
549 | ```bash
550 | $ let "v1 = 1" "v2 = v1++"
551 | $ echo $v1,$v2
552 | 2,1
553 | ```
554 |
555 | 上面例子中,`let`声明了两个变量`v1`和`v2`,其中`v2`等于`v1++`,表示先返回`v1`的值,然后`v1`自增。
556 |
557 | 这种语法支持的运算符,参考《Bash 的算术运算》一章。
558 |
559 |
--------------------------------------------------------------------------------
/loppo.yml:
--------------------------------------------------------------------------------
1 | dir: docs
2 | output: dist
3 | site: Bash 脚本教程
4 | theme: wangdoc
5 | customization: false
6 | themeDir: loppo-theme
7 | direction: ltr
8 | id: bash
9 | hasComments: true
10 | isTutorial: true
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bash-tutorial",
3 | "version": "1.0.0",
4 | "description": "本教程介绍 Linux 命令行 Bash 的基本用法和脚本编程。",
5 | "main": "index.js",
6 | "directories": {
7 | "doc": "docs"
8 | },
9 | "scripts": {
10 | "build": "loppo --site \"Bash 脚本教程\" --id bash --theme wangdoc",
11 | "build-and-commit": "npm run build && npm run commit",
12 | "commit": "gh-pages --dist dist --dest dist/bash --branch master --repo git@github.com:wangdoc/website.git",
13 | "chapter": "loppo chapter",
14 | "server": "loppo server"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/wangdoc/bash-tutorial.git"
19 | },
20 | "keywords": [
21 | "tutorial",
22 | "Bash"
23 | ],
24 | "author": "",
25 | "license": "CC-BY-SA-4.0",
26 | "bugs": {
27 | "url": "https://github.com/wangdoc/bash-tutorial/issues"
28 | },
29 | "homepage": "https://github.com/wangdoc/bash-tutorial#readme",
30 | "dependencies": {
31 | "gh-pages": "6.x",
32 | "loppo": "^0.6.25",
33 | "loppo-theme-wangdoc": "^0.7.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/wangdoc-deploy-rsa.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangdoc/bash-tutorial/9e6450fd747ad6bcc6f8534316e807568daac394/wangdoc-deploy-rsa.enc
--------------------------------------------------------------------------------