本文将会介绍以 bash 作为脚本语言的一些基本操作,以及几种常用的 shell 工具。
目录如下:
我们可以在 bash 中为某个变量赋值,然后使用 echo 将其输出,但需要注意输出的格式,需要在该变量之前添加一个$
符号。
需要注意的是:等号两边不能含有空格,变量名不能以数字开头,不能使用标点符号,不能使用 bash 中的关键字。仅允许使用字母、数字、下划线。
1
2
3
4
|
carol@ubuntu-carol:~$ foo=bar
carol@ubuntu-carol:~$ echo $foo
bar
carol@ubuntu-carol:~$
|
此外,还需要注意单引号和双引号的区别,如下所示。
双引号中的语句是会被解释执行的,而单引号不可以。
1
2
3
4
5
6
7
|
carol@ubuntu-carol:~$ echo $foo
bar
carol@ubuntu-carol:~$ echo "Value is $foo"
Value is bar
carol@ubuntu-carol:~$ echo 'Value is $foo'
Value is $foo
carol@ubuntu-carol:~$
|
这里编写一个 bash 脚本,其功能是:用于创建一个指定名称的目录,然后进入到该目录中。
这里的"$1"
相当于占位符,接收一个目录的名称。
1
2
3
4
|
mcd() {
mkdir -p "$1"
cd "$1"
}
|
1
2
3
4
5
|
carol@ubuntu-carol:~/learnShell$ source mcd.sh
carol@ubuntu-carol:~/learnShell$ mcd test
carol@ubuntu-carol:~/learnShell/test$ pwd
/home/carol/learnShell/test
carol@ubuntu-carol:~/learnShell/test$
|
此外,对于$_
的使用,它所指的是上一个命令中的最后一个参数,或者说当前 shell 环境中的最后一个参数。
对于以下环境来说,当前 shell 环境中的最后一个参数是mkdir a b
中的b
。因此,下一句cd $_
中的$_
指的就是b
。
1
2
3
4
5
6
7
8
9
|
carol@ubuntu-carol:~/learnShell$ ls
hello2.txt hello.txt ls.txt mcd.sh number.txt
carol@ubuntu-carol:~/learnShell$ mkdir file
carol@ubuntu-carol:~/learnShell$ cd $_
carol@ubuntu-carol:~/learnShell/file$
carol@ubuntu-carol:~/learnShell/file$ cd ..
carol@ubuntu-carol:~/learnShell$ mkdir a b
carol@ubuntu-carol:~/learnShell$ cd $_
carol@ubuntu-carol:~/learnShell/b$
|
当我们执行grep foobar mcd.sh
时,也就是尝试通过grep
在mcd.sh
脚本中寻找foorbar
时,由于匹配不到相应的foobar
,因此在echo $?
之后,会返回一个错误信息1
(事实上,在错误时会返回一个在 1~255 内的整数)。如果上一步没有执行错误,说明执行成功,则会返回信息0
。例如,在执行echo "hello"
后,由于没有执行错误,因此返回的是0
。
1
2
3
4
5
6
7
8
9
10
|
carol@ubuntu-carol:~/learnShell$ ls
hello2.txt hello.txt ls.txt mcd.sh number.txt
carol@ubuntu-carol:~/learnShell$ echo "hello"
hello
carol@ubuntu-carol:~/learnShell$ echo $?
0
carol@ubuntu-carol:~/learnShell$ grep foobar mcd.sh
carol@ubuntu-carol:~/learnShell$ echo $?
1
carol@ubuntu-carol:~/learnShell$ $?
|
这很好理解,如果我们在mcd.sh
脚本中寻找是否有字符串与dir
匹配,那么此时是匹配成功的,然后执行echo $?
会返回0
,如下所示:
1
2
3
4
5
|
carol@ubuntu-carol:~/learnShell$ grep dir mcd.sh
mkdir -p "$1"
carol@ubuntu-carol:~/learnShell$ echo $?
0
carol@ubuntu-carol:~/learnShell$
|
当然,我们也可以通过true
和false
来判断echo $?
的输出,如下所示:
1
2
3
4
5
6
7
|
carol@ubuntu-carol:~/learnShell$ true
carol@ubuntu-carol:~/learnShell$ echo $?
0
carol@ubuntu-carol:~/learnShell$ false
carol@ubuntu-carol:~/learnShell$ echo $?
1
carol@ubuntu-carol:~/learnShell$
|
甚至还可以结合逻辑运算符,如下所示:
注意短路运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
carol@ubuntu-carol:~/learnShell$ false || echo "hello"
hello
carol@ubuntu-carol:~/learnShell$ echo $?
0
carol@ubuntu-carol:~/learnShell$ false && echo "hello"
carol@ubuntu-carol:~/learnShell$ echo $?
1
carol@ubuntu-carol:~/learnShell$ true || echo "hello"
carol@ubuntu-carol:~/learnShell$ echo $?
0
carol@ubuntu-carol:~/learnShell$ true && echo "hello"
hello
carol@ubuntu-carol:~/learnShell$ echo $?
0
carol@ubuntu-carol:~/learnShell$
|
我们可以在 shell 中使用变量去承接一个命令的输出。不对,准确的说,应该是使用变量foo
作为pwd
命令的一个别名。如下所示:
1
2
3
4
|
carol@ubuntu-carol:~/learnShell$ foo=$(pwd)
carol@ubuntu-carol:~/learnShell$ echo $foo
/home/carol/learnShell
carol@ubuntu-carol:~/learnShell$
|
使用重定向符号<
并搭配命令行语句ls
,来列出当前目录下以及父级目录下所有的文件,如下所示:
需要注意的是:<(
之间不能有空格,你应该将它们看作是同一个语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
carol@ubuntu-carol:~/learnShell$ cat <(ls) <(ls ..)
hello2.txt
hello.txt
ls.txt
mcd.sh
number.txt
Desktop
Documents
Downloads
learnC
learnShell
Music
Pictures
Public
snap
Templates
Videos
carol@ubuntu-carol:~/learnShell$
|
下面这段脚本用于遍历我们提供的参数,使用grep
搜索字符串foobar
,如果没有找到的话,则将其作为注释追加到文件中。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
echo "Startint program at $(date)"
echo "Running program $0 with $# arguments with pid $$"
for file in "$@"; do
grep foobar "$file" > /dev/null 2> /dev/null
# 如果我们想要匹配的模式没有找到,则 grep 退出状态会为 1
# 我们将标准输出流和标准错误流重定向到 Null,因为我们并不关心这些信息
if [[ "$?" -ne 0 ]]; then
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
if
done
|
1
2
3
4
|
import sys
for arg in reversed(sys.argv[1:]):
print(arg)
|
执行完./example.sh mcd.sh script.py example.sh
语句后可以发现,mcd.sh
中没有foobar
语句,因此程序会在mcd.sh
中的末尾添加# foobar
语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
carol@ubuntu-carol:~/learnShell$ chmod +777 example.sh
carol@ubuntu-carol:~/learnShell$ ./example.sh mcd.sh script.py example.sh
Startint program at Thu 26 Nov 2020 09:30:12 PM CST
Running program ./example.sh with 3 arguments with pid 7787
File mcd.sh does not have any foobar, adding one
File script.py does not have any foobar, adding one
carol@ubuntu-carol:~/learnShell$ cat mcd.sh
mcd() {
mkdir -p "$1"
cd "$1"
}
# foobar
carol@ubuntu-carol:~/learnShell$
|
接下来就是一些通配符的使用了,其中*
用于匹配多个字符,而?
用于匹配一个字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
carol@ubuntu-carol:~/learnShell$ tree
.
├── example.sh
├── hello2.txt
├── hello.txt
├── ls.txt
├── mcd.sh
├── number.txt
├── project1
│ └── src
├── project2
│ └── src
├── project42
│ └── src
└── script.py
6 directories, 7 files
carol@ubuntu-carol:~/learnShell$ ls project*
project1:
src
project2:
src
project42:
src
carol@ubuntu-carol:~/learnShell$ ls project?
project1:
src
project2:
src
carol@ubuntu-carol:~/learnShell$
|
对于花括号的使用,这个知识点我之前没接触过,在这里记录一下。
也就是说,当你有一系列命令的时候,其中包含一段公共的字串时,可以使用花括号来自动展开这些命令。如下所示:
1
2
3
4
5
6
7
|
carol@ubuntu-carol:~/learnShell$ ls
example.sh hello2.txt hello.txt image.jpg image.png ls.txt mcd.sh number.txt project1 project2 project42 script.py
carol@ubuntu-carol:~/learnShell$ cp image.{jpg,png} /home/carol/
carol@ubuntu-carol:~/learnShell$ cd /home/carol/
carol@ubuntu-carol:~$ ls
Desktop Documents Downloads image.jpg image.png learnC learnShell Music Pictures Public snap Templates Videos
carol@ubuntu-carol:~$
|
其中:语句cp image.{jpg,png} /home/carol/
被自动展开为:
1
|
cp image.jpg image.png /home/carol/
|
需要注意的是:image.{jpg,png}
中的逗号两边不能含有空格。
此外,如果想要使用例如touch
创建多个含有公共前缀文件名的文件的话,则也可以使用{ }
,例如:
1
2
3
4
5
6
7
|
carol@ubuntu-carol:~/learnShell/project1/src$ touch foo{1,2,3}
carol@ubuntu-carol:~/learnShell/project1/src$ ls -l
total 0
-rw-rw-r-- 1 carol carol 0 Nov 28 16:02 foo1
-rw-rw-r-- 1 carol carol 0 Nov 28 16:02 foo2
-rw-rw-r-- 1 carol carol 0 Nov 28 16:02 foo3
carol@ubuntu-carol:~/learnShell/project1/src$
|
如果在逗号两侧含有空格的话,则会发生意想不到的结果,这显然不是我们的目的。例如:
1
2
3
4
5
6
7
8
9
10
|
carol@ubuntu-carol:~/learnShell/project1/src$ touch foo{4, 5, 6}
carol@ubuntu-carol:~/learnShell/project1/src$ ls -l
total 0
-rw-rw-r-- 1 carol carol 0 Nov 28 16:04 5,
-rw-rw-r-- 1 carol carol 0 Nov 28 16:04 6}
-rw-rw-r-- 1 carol carol 0 Nov 28 16:02 foo1
-rw-rw-r-- 1 carol carol 0 Nov 28 16:02 foo2
-rw-rw-r-- 1 carol carol 0 Nov 28 16:02 foo3
-rw-rw-r-- 1 carol carol 0 Nov 28 16:04 foo{4,
carol@ubuntu-carol:~/learnShell/project1/src$
|
甚至可以在多个目录下创建多个文件,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
carol@ubuntu-carol:~/learnShell/project1/src$ mkdir foo bar
carol@ubuntu-carol:~/learnShell/project1/src$ ls
bar foo
carol@ubuntu-carol:~/learnShell/project1/src$ touch {foo,bar}/{a..j}
carol@ubuntu-carol:~/learnShell/project1/src$ tree
.
├── bar
│ ├── a
│ ├── b
│ ├── c
│ ├── d
│ ├── e
│ ├── f
│ ├── g
│ ├── h
│ ├── i
│ └── j
└── foo
├── a
├── b
├── c
├── d
├── e
├── f
├── g
├── h
├── i
└── j
2 directories, 20 files
carol@ubuntu-carol:~/learnShell/project1/src$
|
这里的touch {foo,bar}/{a..j}
命令会被展开成touch foo/a foo/b foo/c ... foo/j bar/a bar/b bar/c ... bar/j
。中间的目录我用...
给省略了。
然后我们可以使用diff
命令查看ls
输出的不同,例如:
1
2
3
4
5
6
|
carol@ubuntu-carol:~/learnShell/project1/src$ touch foo/x foo/y
carol@ubuntu-carol:~/learnShell/project1/src$ diff <(ls foo) <(ls bar)
11,12d10
< x
< y
carol@ubuntu-carol:~/learnShell/project1/src$
|
可以看到,在foo
目录下和bar
目录下的文件中,只有x
和y
文件不同。
在 Linxu 中,可以使用man
查看一个某个命令的使用说明,但是man page
页面介绍的太罗嗦了。因此,这里有一个实用的工具 tldr,即「Too long; Don’t Read.」,可以简化地介绍某个命令,并给出一些事例。
使用find
命令在当前目录下,查找名为src
的目录,例如:
1
2
3
4
5
6
7
|
carol@ubuntu-carol:~/learnShell$ ls
example.sh hello2.txt hello.txt image.jpg image.png ls.txt mcd.sh number.txt project1 project2 project42 script.py
carol@ubuntu-carol:~/learnShell$ find . -name src -type d
./project2/src
./project42/src
./project1/src
carol@ubuntu-carol:~/learnShell$
|
对于find
命令的其它功能,例如查找当前目录下所有以.tmp
结尾的文件,并执行删除操作,如下:
1
|
find . -name '*.tmp' -exec rm {} \;
|
也就是说,在执行find
命令后,使用参数-exec
再跟上另外一个命令,可以实现多个命令组合的效果,例如:找到所有的.png
文件,然后将它们都转换成.jpg
格式,如下:
1
|
find . -name '*png' -exec convert {} {.}.jpg \;
|
之前可以使用grep
进行文件中的字符串的查找,也可以使用工具fzf
进行查找,如下所示:
1
|
carol@ubuntu-carol:~/learnShell$ cat example.sh | fzf
|
输入完上述命令后,则会进入编辑模式。然后,你可以在该模式下输入你想要查找的字符串。
对于可视化目录来说,也有几个比较实用的工具,如 tree、broot、nnn 等。