课程结构
01.课程概览与 shell
02.Shell 工具和脚本
03.编辑器 (Vim)
04.数据整理
05.命令行环境
06.版本控制(Git)
07.调试及性能分析
08.元编程
09.安全和密码学
10.大杂烩
11.提问&回答
本文档修改自这里 ,补充了一些视频中展示但配套文档中未提供的代码,以及一些注释。
shell命令&参数&环境变量
几乎所有您能够接触到的平台都支持某种形式的 shell,其核心功能是:它允许你执行程序,输入并获取某种半结构化的输出。
本节课我们会使用 Bourne Again SHell, 简称 “bash”。
当您打开终端时,您会看到一个提示符,它看起来一般是这个样子的:
missing:~$
这是 shell 最主要的文本接口,含义是主机名为 missing
且当前的工作目录(”current working directory”)是 ~
(home,主目录)。
$
符号表示您现在的身份不是 root 用户(root用户的符号为#
)。在这个提示符中,您可以输入 命令。执行 date
命令,打印出了当前的日期和时间。
missing:~$ date
Fri 10 Jan 2020 11:49:31 AM EST
missing:~$ echo hello
hello
上例中,我们让 shell 执行 echo
,同时指定参数 hello
。echo
程序将该参数打印出来。 shell 基于空格分割命令并进行解析,然后执行第一个单词代表的程序,并将后续的单词作为程序可以访问的参数。
~$ echo "Hello world"
Hello world
# echo 将字符串置于标准输出,类似于c的printf函数
~$ echo Hello\ world
Hello world
# "\ "表示中间的空格"Hello world"是一个参数,而非两个参数Hello和world
# 当一行命令过长时,也可使用\进行分隔
~$ echo Hello world
Hello world
# 注意:对于其他命令,可能会将Hello和world当作两个参数,从而得到预期外的效果
但是,shell 是如何知道去哪里寻找 date
或 echo
的呢?其实,类似于 Python 或 Ruby,shell 是一个编程环境,所以它具备变量、条件、循环和函数。当要求 shell 执行某个指令,它会去咨询 环境变量 $PATH
。
~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# 显示的结果使用 : 分隔多个不同路径
~$ echo ls $PATH
ls /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# 查询结果的显示顺序为shell查找ls程序的搜索顺序
missing:~$ which echo
/bin/echo
# 显示当前目录下执行的echo命令的所在目录
~$ which -a echo
/usr/bin/echo
/bin/echo
# 参数 -a 打印echo的所有路径
~$ /bin/echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# 指定路径/bin/echo下的echo程序打印出环境变量
在shell中导航
shell 中的路径是一组被分割的目录,在 Linux 和 macOS 上使用 /
分割,而在Windows上是 \
。路径 /
代表的是系统的根目录,所有的文件夹都包括在这个路径之下,在Windows上每个盘都有一个根目录(例如: C:\
)。
如果某个路径以 /
开头,那么它是一个 绝对路径,其他的都是 相对路径 。相对路径是指相对于当前工作目录的路径,当前工作目录可以使用 pwd
命令来获取。此外,切换目录需要使用 cd
命令。在路径中,.
表示的是当前目录,而 ..
表示上级目录:
missing:~$ pwd
/home/missing
# 显示当前工作目录,这里展示的是"~"代表的主目录
missing:~$ cd /home
# 切换工作目录至"/home",见下行"missing:"后、"$"前
missing:/home$ pwd
/home
missing:/home$ cd ..
# 切换到上一级目录,即根目录"/",在linux文件管理器点击“文件系统”,即可进入根目录
missing:/$ pwd
/
missing:/$ cd ./home
missing:/home$ pwd
/home
missing:/home$ cd missing
missing:~$ pwd
/home/missing
missing:~$ ../../bin/echo hello
hello
为了查看指定目录下包含哪些文件,我们使用 ls
命令:
missing:~$ ls
missing:~$ cd ..
missing:/home$ ls
missing
missing:/home$ cd ..
missing:/$ ls
bin
boot
dev
etc
home
...
除非我们利用第一个参数指定目录,否则 ls
会打印当前目录下的文件。大多数的命令接受标记和选项(带有值的标记),它们以 -
开头,并可以改变程序的行为。通常,在执行程序时使用 -h
或 --help
标记可以打印帮助信息,以便了解有哪些可用的标记或选项。
# -l : use a long listing format
missing:~$ ls -l /home
drwxr-xr-x 1 missing users 4096 Jun 15 2019 missing
- 字符串
drwxr-xr-x
的第一个字符表示文件类型,d
说明这是一个目录directory - 后面的9个字符,每三个为一组,
r
表示读取权限;w
表示写入权限;x
表示执行权限;-
表示没有对应权限 - 前三个字符表示所有者(
missing
)的权限,中间三个字符表示所属组(users
)的权限,后三个字符表示其他用户的权限
在这个阶段,还有几个趁手的命令是您需要掌握的。
mv
(重命名或移动文件)
- Rename a file or directory when the target is not an existing directory:
mv {{source}} {{target}}
- Move a file or directory into an existing directory:
mv {{source}} {{existing_directory}}
- Move multiple files into an existing directory, keeping the filenames unchanged:
mv {{source1}} {{source2}} {{source3}} {{existing_directory}}
- Do not prompt for confirmation before overwriting existing files:
mv -f {{source}} {{target}}
- Prompt for confirmation before overwriting existing files, regardless of file permissions:
mv -i {{source}} {{target}}
- Do not overwrite existing files at the target:
mv -n {{source}} {{target}}
- Move files in verbose mode, showing files after they are moved:
mv -v {{source}} {{target}}
cp
(拷贝文件)
- Copy a file to another location:
cp {{path/to/source_file.ext}} {{path/to/target_file.ext}}
- Copy a file into another directory, keeping the filename:
cp {{path/to/source_file.ext}} {{path/to/target_parent_directory}}
- Recursively copy contents of a directory to another location (if the destination exists, the directory is copied inside it):
cp -R {{path/to/source_directory}} {{path/to/target_directory}}
- Copy a directory recursively, in verbose mode (shows files as they are copied):
cp -vR {{path/to/source_directory}} {{path/to/target_directory}}
- Copy multiple files at once to a directory:
cp -t {{path/to/destination_directory}} {{path/to/file1 path/to/file2 ...}}
- Copy text files to another location, in interactive mode (prompts user before overwriting):
cp -i {{*.txt}} {{path/to/target_directory}}
- Follow symbolic links before copying:
cp -L {{link}} {{path/to/target_directory}}
- Use the first argument as the destination directory (useful for xargs ... | cp -t <DEST_DIR>):
cp -t {{path/to/target_directory}} {{path/to/file_or_directory1 path/to/file_or_directory2 ...}}
mkdir
(新建文件夹)
- Create specific directories:
mkdir {{path/to/directory1 path/to/directory2 ...}}
- Create specific directories and their [p]arents if needed:
mkdir -p {{path/to/directory1 path/to/directory2 ...}}
- Create directories with specific permissions:
mkdir -m {{rwxrw-r--}} {{path/to/directory1 path/to/directory2 ...}}
文件重定向与管道
在 shell 中,程序有两个主要的“流”:输入流和输出流。 当程序尝试读取信息时,从输入流中进行读取;当程序打印信息时,它们会将信息输出到输出流中。 通常,一个程序的输入输出流都是您的终端。也就是,您的键盘作为输入,显示器作为输出。 但是,我们也可以重定向这些流!
- 最简单的重定向是
< file
和> file
。这两个命令可以将程序的输入输出流分别重定向到文件:
missing:~$ echo hello > hello.txt
# 将字符串“hello”输出(即写入到)hello.txt(没有该文件会自动创建)
missing:~$ cat hello.txt # 读取hello.txt的内容
hello
missing:~$ cat < hello.txt # 从hello.txt文件读取
hello
missing:~$ cat < hello.txt > hello2.txt # 从hello.txt读取内容,将其输出到hello2.txt,hello2.txt如果原本有内容,会被覆写
missing:~$ cat hello2.txt
hello
~$ echo hello >> hello.txt # 使用 >> 会将内容添加到文件末尾
~$ cat hello.txt
hello
hello
- 使用管道(
pipes
),我们能够更好的利用文件重定向。|
操作符将左侧程序的输出,作为右侧程序的输入。
missing:~$ ls -l / | tail -n1
drwxr-xr-x 1 root root 4096 Jun 20 2019 var
# 先在根目录“/”执行"ls -l"
# 对得到的结果,执行tail -n1,即输出最后一行
missing:~$ curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2
219
# curl获取网址google.com的head内容
# grep在curl返回的内容中查找含有"content-length"的行(查找时不区分大小写)
# 对找到的行,以' '为分界符,显示第二项,若'-f2'改为'-f1',会输出'Content-Length:'
如果想更清晰地了解上一行的命令,可以逐个管道执行,即:
~ $ curl --head --silent google.com
HTTP/1.1 301 Moved Permanently
Content-Length: 219
Cache-Control: public, max-age=2592000
Connection: keep-alive
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-ZXOmXF_8KLGeCJbHqzaZ2Q' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Content-Type: text/html; charset=UTF-8
Date: Wed, 26 Apr 2023 14:58:10 GMT
Expires: Fri, 26 May 2023 14:58:10 GMT
Keep-Alive: timeout=4
Location: http://www.google.com/
Proxy-Connection: keep-alive
Server: gws
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 0
~ $ curl --head --silent google.com | grep --ignore-case content-length
Content-Length: 219
~ $ curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2
219
root权限
对于大多数的类 Unix 系统,有一类用户是非常特殊的,那就是:根用户(root user)。 根用户可以创建、读取、更新和删除系统中的任何文件(即所有权限)。
~ $ sudo su
# 输入密码后,即可以根用户身份执行命令
root@PCname:/home/usrname#
# 上行最后的" # " 就是根用户的符号
一般而言,sudo <cmd>
将以根用户权限执行<cmd>
。
有一件事情是您必须作为根用户才能做的,那就是向 sysfs
文件写入内容。系统被挂载在 /sys
下,sysfs
文件则暴露了一些内核(kernel)参数。 因此,您不需要借助任何专用的工具,就可以轻松地在运行期间配置系统内核。注意 Windows 和 macOS 没有这个文件
例如,您笔记本电脑的屏幕亮度写在 brightness
文件中,它位于
/sys/class/backlight
通过将数值写入该文件,我们可以改变屏幕的亮度。
$ sudo find -L /sys/class/backlight -maxdepth 2 -name '*brightness*'
/sys/class/backlight/thinkpad_screen/brightness
$ cd /sys/class/backlight/thinkpad_screen
$ sudo echo 3 > brightness
An error occurred while redirecting file 'brightness'
open: Permission denied
注意:管道|
和重定向>
、<
是通过 shell 执行的,而不是被各个程序单独执行。
命令sudo echo 3 > brightness
应分解为:1. shell (权限为您的当前用户) 尝试打开brightness文件(并删除原有文件中的内容);2. sudo echo
让echo
程序以root权限,将参数 3 写入brightness
文件。
然而,执行第一步时,shell没有root权限,无法打开brightness
文件
明白这一点后,我们可以这样操作:
$ echo 3 | sudo tee brightness
因为打开 /sys
文件的是 tee
这个程序,并且该程序以 root
权限在运行,因此操作可以进行。 这样您就可以在 /sys
中愉快地玩耍了,例如修改系统中各种LED的状态(路径可能会有所不同):
$ echo 1 | sudo tee /sys/class/leds/input6::scrolllock/brightness
课后练习
-
本课程需要使用类Unix shell,例如 Bash 或 ZSH。如果您在 Linux 或者 MacOS 上面完成本课程的练习,则不需要做任何特殊的操作。如果您使用的是 Windows,则您不应该使用 cmd 或是 Powershell;您可以使用Windows Subsystem for Linux或者是 Linux 虚拟机。使用
echo $SHELL
命令可以查看您的 shell 是否满足要求。如果打印结果为/bin/bash
或/usr/bin/zsh
则是可以的。 -
在
/tmp
下新建一个名为missing
的文件夹。 -
用
man
查看程序touch
的使用手册。 -
用
touch
在missing
文件夹中新建一个叫semester
的文件。 -
将以下内容一行一行地写入
semester
文件:#!/bin/sh curl --head --silent https://missing.csail.mit.edu
第一行可能有点棘手,
#
在Bash中表示注释,而!
即使被双引号("
)包裹也具有特殊的含义。 单引号('
)则不一样,此处利用这一点解决输入问题。更多信息请参考 Bash quoting 手册 -
尝试执行这个文件。例如,将该脚本的路径(
./semester
)输入到您的shell中并回车。如果程序无法执行,请使用ls
命令来获取信息并理解其不能执行的原因。 -
查看
chmod
的手册(例如,使用man chmod
命令) -
使用
chmod
命令改变权限,使./semester
能够成功执行,不要使用sh semester
来执行该程序。您的 shell 是如何知晓这个文件需要使用sh
来解析呢?更多信息请参考:shebang -
使用
|
和>
,将semester
文件输出的最后更改日期信息,写入主目录下的last-modified.txt
的文件中 -
写一段命令来从
/sys
中获取笔记本的电量信息,或者台式机 CPU 的温度。注意:macOS 并没有 sysfs,所以 Mac 用户可以跳过这一题。
习题解答
-
在 /tmp 下新建一个名为 missing 的文件夹。
~ $ cd /tmp # / 表示根目录,即:将当前工作目录切换到根目录下的tmp目录 # ~ 表示主目录,一般路径为:/home/usrname # 这里usrname是你为当前电脑用户所取的名字,同时也是home目录下的一个子目录 /tmp $ mkdir missing /tmp $ ls
-
用 man 查看程序 touch 的使用手册。
~ $ man touch # 执行命令,会打开新的一个缓存页面,可以使用鼠标键盘上下浏览,或输入 q 退出
一般来说,
man
命令返回的是一个冗长的手册(manual),如果想要快速了解一个命令的使用方法,可以尝试安装下面的包~ $ sudo apt install tldr ~ $ sudo tldr -u # 更新数据库 ~ $ sudo tldr touch # 查询结果中,包含示例,可以直接复制后修改使用
-
用 touch 在 missing 文件夹中新建一个叫 semester 的文件。
~ $ cd /tmp/missing /tmp/missing $ touch semester /tmp/missing $ ls
-
将以下内容一行一行地写入 semester 文件:
#!/bin/sh curl --head --silent https://missing.csail.mit.edu
第一行可能有点棘手,
#
在Bash中表示注释,而!
即使被双引号("
)包裹也具有特殊的含义。单引号('
)则不一样,此处利用这一点解决输入问题。更多信息请参考 Bash quoting手册(shell中
!
表示事件指示符,即对历史列表中命令行条目的引用)~ $ cd /tmp/missing /tmp/missing $ echo '#!/bin/sh' > semester /tmp/missing $ echo curl --head --silent https://missing.csail.mit.edu >> semester /tmp/missing $ cat semester
-
尝试执行这个文件。即,将该脚本的路径(
./semester
)输入到您的shell中并回车。如果程序无法执行,请使用 ls 命令来获取信息并理解其不能执行的原因。/tmp/missing $ ./semester bash: permission denied: ./semester /tmp/missing $ ls -l total 8 -rw-r--r-- 1 username wheel 61 4 24 17:12 semester
从
ls -l
的返回结果,可知:当前用户对semester
文件没有执行权限x
-
查看 chmod 的手册(例如,使用
man chmod
命令)/tmp/missing $ man chmod
-
使用 chmod 命令改变权限,使
./semester
能够成功执行,不要使用 sh semester 来执行该程序。您的 shell 是如何知晓这个文件需要使用 sh 来解析呢?更多信息请参考:shebang/tmp/missing $ chmod 777 semester /tmp/missing $ ls -l total 8 -rwxrwxrwx 1 username wheel 61 4 24 17:12 semester /tmp/missing $ ./semester # 返回网址的head
chmod
允许使用数字代表权限,1
为执行权限x
,2
为只写权限w
,4
为只读权限r
,7
即为拥有三种权限,你也可以尝试chmod -x semester
,再执行ls -l
查看权限的变化。补充:
shebang
一般写在脚本文件的开头,格式如下#! interpreter [optional-arg]
其中,
interpreter
是指向一个可执行程序的路径(如windows中python的exe文件位置),后面的optional-arg
是一个可选参数(可以不用写)
shebang
命令的作用是:在终端使用sourece semester
或./semester
执行脚本semester
时,告知bash
或zsh
,需要调用哪个解释器(interpreter
)来运行脚本。 -
使用
|
和>
,将 semester 文件输出的最后更改日期信息,写入主目录下的last-modified.txt
的文件中./tmp/missing $ ./semester | grep --ignore-case last-modified > ~/last-modified.txt /tmp/missing $ cat last-modified.txt last-modified: Sun, 23 Apr 2023 23:34:17 GMT
-
写一段命令来从 /sys 中获取笔记本的电量信息,或者台式机 CPU 的温度。注意:macOS 并没有 sysfs,所以 Mac 用户可以跳过这一题
Windows 用户可以通过以下命令查询:
~$ cat /sys/class/power_supply/battery/capacity 100
Linux 用户可以通过以下命令查询:
~$ ls /sys/class/power_supply ADP0 BAT0 ~$ cat /sys/class/power_supply/BAT0/capacity 100
- 不同电脑使用的目录可能不同,例如ls命令结果:ACAD BAT1,或是其他类似结果
- ACAD或ADP0:记录电源适配器相关的信息,如…/ADP0/online为1,表示处于充电状态