也许是电脑最原始的交互方式
Shell 介绍
现在的电脑有非常多的交互方式,图形,声音甚至是 AR/VR。但是他们基本都限制了你能做什么,也就是你只能使用已有的功能,而不能去执行一些不存在的功能。
为了更好的使用电脑,我们需要回到最原始的文本交互方式,也就是 Shell
Shell 也是一种编程语言,你可以在 Shell 中实现 while 循环,for 循环,条件语句,甚至你可以编写函数等等
Missing semester master 课程中老师使用的是 Bourne Again Shell
Shell 的简单使用
当打开终端的的时候可以看到 Shell 的提示符
1 | ekko@Ling:~/桌面$ |
ekko@Ling
用户名@机器名称~/桌面
当前所在路径~
home的简写$
表示你不是根用户(tells you that you are not the root user)
你可以在 $
后输入命令(你想要让 Shell 做的事情)
命令通常是非常简单的东西,像是给定参数,然后执行程序
参数就是在命令后面一空格分隔的东西
执行
date
这个程序1
2ekko@Ling:~/桌面$ date
2023年 06月 20日 星期二 21:27:21 CST你也可以带有参数的执行程序,这是修改程序行为的一种方式,比如使用
echo
1
2ekko@Ling:~/桌面$ echo Hello
Helloecho
会打印参数,当你的参数是带有空格的时候,可以用引号将参数括起来,也可以使用\
将空格转义当然如果你坚持不转义空格,或者使用引号将被空格分开的参数括起来,对于
echo
命令来说也没有什么大不了,但是如果你想要创建一个名称为 “my photos” 的文件夹时,就会出现问题1
2ekko@Ling:~/桌面$ mkdir "my photos"
ekko@Ling:~/桌面$ mkdir my photos第一条命令,将会正确创建 “my photos” 的文件夹
第二条命令,将会创建两个文件夹,“my” 和 “photos”
一个自然的问题,Shell
是如何知道怎样执行所输入的程序呢?(也就是他为什么接收到
echo
就会打印他的参数呢?)
原因是,有一些内置程序是计算机自带的,还会附带一些面向终端的应用程序,这些应用程序,存储在你的文件系统中,Shell 有一种方法可以确定程序的位置,通过使用环境变量。
环境变量
环境变量是在启动 Shell 时设置的东西,它们并不是你每次运行 Shell 时都必须设置的东西。
有一些已经被设置好的变量,像是你的主目录在哪里,你的用户名是什么...
要查看搜索路径,有一个至关重要的变量,即路径变量
$PATH
路径变量同样我们可以使用
echo
将其打印1
2ekko@Ling:~/桌面$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin将会显示所有的路径列表,Shell 就是在这些路径里搜索程序的
这是一个用
:
分隔的列表,每次你键入命令,它都会在计算机上搜索这个路径列表,并在每个目录中查找,与你尝试运行的命令匹配的程序或文件的名称如果想要知道实际运行的是哪个程序,可以使用
which
命令1
2ekko@Ling:~/桌面$ which echo
/usr/bin/echo他告诉我,如果我想要运行
echo
,将会运行这个路径下的程序
路径
路径是一种描述计算机上文件位置的方式
在 Linux 和 macOS 上,这些路径是由斜杠 /
分隔的
在 Windows 上,这样的路径通常用反斜杠 \
分隔
在 Linux 和 macOS,所有内容都属于根命名空间,因此所有路径都以斜杠或绝对路径开头;在 Windows 上,每个分区都有一个根目录,因此你可能看到 “C:\” 或 “D:\” 的路径
1 | /usr/bin/echo |
对于这样一个路径,从根目录出发,查看名为 “usr” 的目录,然后查看名为 “bin” 的目录,然后查看名为 “echo” 的文件
- 绝对路径:能够完全确定文件位置的路径
- 相对路径:相对于你当前所在的位置的路径
想要找出当前位置,可以使用 pwd
命令(print working
directory)
它将打印出我当前所在的路径
1 | ekko@Ling:~/桌面$ pwd |
所有的相对路径都是相对于当前工作目录而言的,同样的,我也可以改变我的当前工作目录,通过使用
cd
命令(change directory)
1 | ekko@Ling:~/桌面$ cd /home |
同时也有几个特殊的目录存在
.
表示当前目录..
表示父目录这也是一个快速切换工作目录的方式
1
2ekko@Ling:~/桌面$ cd ..
ekko@Ling:~$快速回到上一级目录
也可以通过相对路径,切换工作目录
1
2ekko@Ling:/$ cd ./home
ekko@Ling:/home$切换到当前目录下的home目录
当然如果切换到没有的目录,他也会给出没有的提示
1
2ekko@Ling:/home$ cd ./home
bash: cd: ./home: No such file or directory
通过构建路径,来遍历文件系统的方法
一般情况下,我们可以任意使用相对路径和绝对路径,使用两者中最简单的
而如果你编写了一个程序,想要在任何情况下都能运行,那么应该给出绝对路径,因为如果给出相对路径,在工作目录切换时,会出现无法成功运行的现象
通常当我们运行程序时,它默认会在当前工作目录下进行操作,除非给出参数,这非常方便,意味着我们通常不需要提供完整路径,我们只需要使用文件名和当前所在的目录。
有些时候我们想要知道当前工作目录下有哪些文件,这时候就需要
ls
命令1
2
3
4ekko@Ling:/$ ls
bin dev lib libx32 mnt root
boot etc lib32 lost+found opt run
cdrom home lib64 media proc sbin快速浏览当前位置文件
或者
ls {路径}
将参数作为路径传入,可以得到对应路径下的所有文件1
2ekko@Ling:/$ ls /home
ekko~
总是拓展到主目录,所以可以使用~
来写相对路径1
2
3ekko@Ling:/$ cd ~
ekko@Ling:~$ pwd
/home/ekko-
切换到之前所在的目录1
2
3
4ekko@Ling:~$ cd -
/
ekko@Ling:/$ cd -
/home/ekko
对于程序的参数,大多数程序会采用所谓的参数,如标志和选项,这些标志和选项通常以
-
开头
--help
大多数程序都实现了这个选项1
2ekko@Ling:~$ ls --help
打印出大量该命令的帮助信息你将看到程序的功能描述,和各种可用的标志和选项的详细说明
通常我们称
-{单个字母}
(单个破折号和单个字母的组合)为标志(flag),不带任何值的内容也是标志,带有值的东西为选项(option)例如:
-a
和--all
都是标志,-C
和--color
是选项-l
ls
命令加该参数,可以提供很多非常有用的额外信息1
2
3
4
5ekko@Ling:/$ ls -l
总用量 945500
lrwxrwxrwx 1 root root 7 6月 19 09:10 bin -> usr/bin
drwxr-xr-x 4 root root 4096 6月 19 09:15 boot
drwxr-xr-x 2 root root 4096 6月 19 09:11 cdrom以上的额外信息
d
表示这些条目是一个目录rwxr-xr-x
之后的字母表示为该文件设置的权限阅读方法是每三个字符为一组,分别是
r
读取(read),w
写入(write),x
执行(execute),-
没有该项权限rwx
前三个字符的第一组为文件的所有者设置的权限r-x
第二组三个字符是为拥有该文件的组设置的权限。在这种情况下,所有这些文件也都是由root组拥有的r-x
最后一组三个字符是其他人的权限列表,所以对于那些不是文件或目录所有者或组所有者的人来说
对于文件来说:
有读取权限,你就可以阅读文件
有写入权限,你就可以保存文件,添加内容,或者替换它
有执行权限,你就可以执行那个文件
对于目录来说,这些权限的含义稍有变化:
- 有读取权限,你被允许看到这个目录中的文件
- 有写入权限,你被允许在该目录中重命名、创建或删除文件
- 有执行权限,是所谓的搜索权限,你被允许进入该目录。也就是如果你想要操作目录中的文件或者使用
cd
命令进入某个目录,你必须拥有该目录及其所有父目录的执行权限
假如,你对一个文件有写入权限,但是对文件所在目录没有写入权限,那么你可以清空这个文件,但是不能删除它,因为清空文件,相当于修改文件,与文件的写入权限有关;而删除文件,是与文件所在目录的写入权限有关
还有
s
,t
,l
(查资料)
mv
将文件从旧路径移动到新路径,可以更改文件名但不改变目录,也可以将文件移动到完全不同的目录中mv {旧路径} {新路径}
1
ekko@Ling:~/桌面$ mv 1 2
将文件名由 1 改成 2
1
ekko@Ling:~/桌面$ mv 1 ./my
将文件 1 移动到当前目录下的my目录中
cp
将第一个路径上的文件复制一份到第二个路径上1
ekko@Ling:~/桌面$ cp hello ./1
rm
删除给定路径上的文件1
ekko@Ling:~/桌面$ rm ./1
Linux 中删除操作不会递归进行,因此不能够通过
rm
操作来删除一个目录可以通过添加
-r
标志来递归删除指定目录下的所有文件1
ekko@Ling:~/桌面$ rm -r ./1
当然可以通过
mkdir
来创建一个目录1
ekko@Ling:~/桌面$ mkdir "new photos"
也可以通过
rmdir
来删除目录1
ekko@Ling:~/桌面$ rmdir newdir
但是该命令只能删除空目录,这是一种安全机制,防止你误删
如果你想要更多关于命令在这些平台上如何工作的信息,有一个非常方便的命令
man
以另一个程序的名称作为参数,给出他的手册页1
2ekko@Ling:~/桌面$ man ls
给出ls的手册页和
ls --help
类似,但是man ls
给出的界面信息会更加适合浏览和阅读,在底部还会给出示例,编写者信息,更多信息的位置等等退出:
h for help or q to quit
cat
打印文件内容1
ekko@Ling:~/桌面$ cat hello.txt
Ctrl-l
便捷的键盘快捷方式,用以清除终端,并回到顶部
以上,我们只讨论了单独运行程序的情况,但是当你组合各种不同的程序时,Shell
的大部分强大功能就会真正体现出来,而不只是简单的 cd
和
ls
,你还可能希望与文件交互,并让文件在不同程序之间传输。这时,我们可以使用
Shell 提供的流的概念来实现。
流 stream
每个程序都有两个主要的流:
- 输入流 input stream
- 输出流 output stream
默认情况下,输入流来自于键盘,基本上是你的终端,你在终端中输入的任何内容都会进入程序;输出流则是每当程序打印输出时,输出的内容就会传递到这个流中。默认情况下,输出流也是指向你的终端的。
Shell 提供了一种重定向这些流的方法,以改变程序的输入/输出指向。
最简单的方法是使用尖括号符号
<>
<
表示将这个程序的输入重定向为这个文件的内容>
表示将前面程序的输出重定向到这个文件中1
ekko@Ling:~/桌面$ echo hello > hello.txt
没有重定向前,将会在回车后命令下方打印“hello”,而重定向后“hello”被打印到 hello.txt 文件中,可以通过
cat
命令验证cat
命令还有一种功能,支持将输入与输出连在一起1
2ekko@Ling:~/桌面$ cat < hello.txt
hello按下回车时,Shell 会打开 hello.txt 读取其中的内容传递给
cat
,此时没有重定向输出,所以直接输出在终端上。利用这个性质,也可以同样实现复制文件的操作
1
ekko@Ling:~/桌面$ cat < hello.txt > hello2.txt
此时的
cat
在默认情况下运行,它自己并不知道这个重定向操作,Shell 使用 hello.txt 作为cat
的命令的输入,并将cat
打印的任何内容写入 hello2.txt 中>>
双向箭头表示追加而不是覆盖1
2ekko@Ling:~/桌面$ cat < hello.txt > hello2.txt
ekko@Ling:~/桌面$ cat < hello.txt >> hello2.txt第一条语句不管执行几次,最后 hello2.txt 都和 hello.txt 文件完全一样,因为使用的是
>
每次输出是覆盖之前的记录第二条语句每执行一次,都会在 Hello2.txt 文件中追加输出
以上命令只是与文件交互的简单方法,但是涉及到 Shell 提供的另一个运算符,即管道运算符时,它们就变得更加有趣了。
管道运算符 |
管道运算符,就是一根竖杠 |
他的作用是将左边程序的输出,作为右边程序的输入
tail
打印输入的最后 n 行 例如打印最后一行,使用 “-n1” 参数(或者 “--line = 1”)1
2ekko@Ling:~/桌面$ ls -l / | tail -n1
drwxr-xr-x 14 root root 4096 2月 23 2022 var注意,这里,
ls
不知道tail
,tail
也不知道ls
,它们是不同的程序,从未被编程为彼此兼容,它们只知道,如何从输入读取并写入输出,然后管道运算符将他们连接在一起,在这种情况下,我希望ls
的输出成为ls
的输入,然后想要tail
的输出仅流到我的终端,因为这里并没有重定向他同样的,我也可以将所需要的输出,定向到一个文件中
1
ekko@Ling:~/桌面$ ls -l / | tail -n1 > log.txt
管道不仅能处理文本,对于图像也能做到处理,甚至于视频,也可以用管道进行数据流的传输,比如,将视频文件通过管道输入到一个 Chromecast 发送程序中,然后将这个程序设置为最后一个,它会以 HTTP 流式传输视频文件到你的 Chromecast 上。
如何以更强大,更有趣的方式使用终端
首先我们要讨论,Linux 和 macOS 系统中的一个重要主题,即 root 用户的概念,root 用户有点像 Windows 上的管理员用户,它的用户 ID 是 0。
root 用户是特殊的,因为他可以在你的系统上随意的做任何事情,即使某个文件对任何人都不可读或不可写,root 仍然可以访问该文件。但是大多数时间,你不会作为超级用户来操作。你会用一些其他名字成为一个用户,这将是你常规情况下使用的用户。因为如果你一直以 root 用户身份操作计算机,如果运行了错误的程序,它们可能会彻底的破坏你的计算机。但偶尔你需要做一些要 root 权限才能做的事。通常情况下,你会使用一个叫做 “sudo” 的程序(或者 “do as su” ),这里的 su 指的是超级用户。这是以超级用户的身份执行操作的一种方式。
通常,你需要在终端中输入 “sudo” 加命令的方式,它将以 root 用户的身份运行该命令
在哪些情况下可能需要使用这样的东西呢?
在你的计算机上有很多特殊的文件,比如一个叫做 sysfs 的文件系统。可以通过
如果你 cd
到这里,你会发现很多并不是你计算机上的文件,而是各种内核参数,内核就像是你的计算机的核心。你可以通过文件来访问各种内核参数。
如果继续进入 “class” 他有许多不同类型的设备可以与之交互,或者可以访问各种队列或各种内部奇怪的旋钮。
由于他们暴露为文件,这意味着我们可以使用到目前为止学到的所有工具来操作他们。
在操作过程中,如果直接利用重定向修改文件,操作会被拒绝
1 | ekko@Ling:/sys/class/backlight/intel_backlight$ echo 500 > brightness |
因为如果要修改内核,是需要根用户的权限的
可以想到的一个解决办法是利用 “sudo”
1 | ekko@Ling:/sys/class/backlight/intel_backlight$ sudo echo 500 > brightness |
但是仍然会被拒绝,为什么呢?
就是之前强调的,输入和输出的重定向不是程序所知道的,当我们将 “ls” 传输到 “tail” 时,“tail” 并不知道 “ls” ,而 “ls” 也不知道 “tail” 这个通道是由 Shell 设置的。
所以在这种情况下,我告诉我的 Shell 运行程序 “sudo” 并使用参数 “echo” 和 “500” ,并将其输出发送到名为 “brightness” 的文件,但是是 Shell 而不是 “sudo” 程序打开了 “brightness” 文件。因此,作为我运行的 Shell ,尝试打开 brightness 文件进行写入,但是不允许这样做。因此,会收到权限被拒绝的错误。
从 Shell 的提示符也可以知道现在的权限
1 | ekko@Ling:/sys/class/backlight/intel_backlight$ # echo 1 > /sys/net/ipv4_forward |
#
表示以 root 身份运行此命令$
表示你不是以 root 身份运行
为了以 root 身份运行终端,我们可以切换到 root 终端
sudo su
切换到 root 终端1
2
3ekko@Ling:/$ sudo su
[sudo] ekko 的密码:
root@Ling:/#表示以root身份运行以下命令
su
是一个复杂的命令,可以让你以超级用户的身份获取一个 Shell输入密码后
enter
可以发现用户名从 ekko 变成了 root,提示符从 $ 变为了 #
如果现在执行
echo 500 > brightness
就可以正常执行
除了以 root 身份运行 Shell,还有一种方法,即
echo 1060 | sudo tee brightness
1
ekko@Ling:/$ echo 1060 | sudo tee brightness
要理解上面的语句,就要明白
tee
命令的作用tee
会将输入内容写入一个文件,同时也将它输出到标准输出。也就是
echo 1060
然后用管道符连接到sudo
运行的tee
中
现在你应该大致了解了如何在终端和 Shell 中操作了,并足够了解这些基本任务的原理。
现在,你不应该再需要使用鼠标点击来查找文件了。
还有一个剩余技巧——打开文件
xdg-open
在适当的程序中打开文件(这可能只适用于 Linux ,MacOS 上可能被称为 “open” )1
ekko@Ling:/$ xdg-open lectures.html
Exercises
确保打开的是正确的 Shell
1
2ekko@Ling:~/桌面$ echo $SHELL
/bin/bash输入
echo $SHELL
得到/bin/bash
或/usr/bin/zsh
的回复touch
用于创建新文件1
ekko@Ling:/tmp/missing$ touch semester
Bash quoting
单引号
' '
:所有 meta 字符的功能均被关闭双引号
" "
:大部分 meta 字符的功能被关闭,除了 $ 等少数字符反斜杠
\
:仅跟着\
后面的 meta 字符被关闭直接运行
./semester
失败,通过ls ./semester -l
查看发现1
2ekko@Ling:/tmp/missing$ ls ./semester -l
-rw-rw-r-- 1 ekko ekko 61 6月 22 20:53 ./semester文件拥有者只有读写权限,无执行权限
使用
sh semester
运行1
2ekko@Ling:/tmp$ sh semester
semester: 2: curl: not found出现该条信息说明没有安装 Curl,使用下面的命令安装
1
ekko@Ling:/tmp$ sudo apt-get install curl
安装后再次运行文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22ekko@Ling:/tmp$ sh semester
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Sat, 10 Jun 2023 13:13:30 GMT
access-control-allow-origin: *
etag: "648476fa-1f86"
expires: Thu, 22 Jun 2023 08:35:27 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: FBF2:6524:169121:17C6C8:64940577
accept-ranges: bytes
date: Thu, 22 Jun 2023 18:20:46 GMT
via: 1.1 varnish
age: 0
x-served-by: cache-tyo11967-TYO
x-cache: HIT
x-cache-hits: 1
x-timer: S1687458046.864056,VS0,VE170
vary: Accept-Encoding
x-fastly-request-id: 3d8dc766de1480ea09cca7f852c151aa205803ce
content-length: 8070程序正常输出
sh
命令可以不必事先设定 Shell 的执行权限,相当于将脚本作为参数传给 bash 来执行,被调用执行而不是文件自己来执行,所以不需要执行权限。chmod
修改文件,目录权限syntax :
chmod [操作对象][操作符][赋予权限][文件名]
操作对象:
u
for user 文件目录所有者,g
for group 文件目录所属用户组,o
for other 其他用户,a
for all 所有用户操作符:
+
添加权限,-
减少权限,=
给定权限权限:
r
读,w
写,x
执行1
2
3
4
5ekko@Ling:/tmp$ ll semester
-rw-rw-r-- 1 ekko ekko 60 6月 23 02:20 semester
ekko@Ling :/tmp$ chmod u+x semester
ekko@Ling :/tmp$ ll semester
-rwxrw-r-- 1 ekko ekko 60 6月 23 02:20 semester*当然可以快速赋予权限
1
ekko@Ling :/tmp$ chmod u=rwx semester
通过使用八进制来表示权限
例子:
r-x
,r-4,w-2,x-1,所以这个权限就可以用 \(4+0+1 = 5\) 来表示对于
rwxr-xrw-
即为 \(756\)1
2ekko@Ling :/tmp$ chmod a=rwx semester
ekko@Ling :/tmp$ chmod 777 semester上述两条给予所有用户所有权限的语句效果是一样的
但我们给予 semester 权限后,也能够直接执行了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25ekko@Ling :/tmp$ chmod u+x semester
ekko@Ling :/tmp$ ll semester
-rwxrw-r-- 1 ekko ekko 60 6月 23 02:20 semester*
ekko@Ling :/tmp$ semester
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Sat, 10 Jun 2023 13:13:30 GMT
access-control-allow-origin: *
etag: "648476fa-1f86"
expires: Thu, 22 Jun 2023 08:35:27 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: FBF2:6524:169121:17C6C8:64940577
accept-ranges: bytes
date: Thu, 22 Jun 2023 18:20:46 GMT
via: 1.1 varnish
age: 0
x-served-by: cache-tyo11967-TYO
x-cache: HIT
x-cache-hits: 1
x-timer: S1687458046.864056,VS0,VE170
vary: Accept-Encoding
x-fastly-request-id: 3d8dc766de1480ea09cca7f852c151aa205803ce
content-length: 8070接下来题目要求我们将 “last-modified” 信息打印
我想到的解决办法是,这行信息,是原文件的前 4 行,也是前 4 行的最后一行
所以相当于用
head
tail
相互传递一下,应该就能解决问题事实证明这样确实可行
1
2ekko@Ling :/tmp$ ./semester | head -n 4 | tail -n 1
last-modified: Sat, 10 Jun 2023 13:13:30 GMT