Shallow Dream

Keep It Simple and Stupid!

0%

What is Shell?

也许是电脑最原始的交互方式

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
    2
    ekko@Ling:~/桌面$ date
    2023年 06月 20日 星期二 21:27:21 CST
  • 你也可以带有参数的执行程序,这是修改程序行为的一种方式,比如使用 echo

    1
    2
    ekko@Ling:~/桌面$ echo Hello
    Hello

    echo 会打印参数,当你的参数是带有空格的时候,可以用引号将参数括起来,也可以使用 \ 将空格转义

  • 当然如果你坚持不转义空格,或者使用引号将被空格分开的参数括起来,对于 echo 命令来说也没有什么大不了,但是如果你想要创建一个名称为 “my photos” 的文件夹时,就会出现问题

    1
    2
    ekko@Ling:~/桌面$ mkdir "my photos"
    ekko@Ling:~/桌面$ mkdir my photos

    第一条命令,将会正确创建 “my photos” 的文件夹

    第二条命令,将会创建两个文件夹,“my” 和 “photos”

一个自然的问题,Shell 是如何知道怎样执行所输入的程序呢?(也就是他为什么接收到 echo 就会打印他的参数呢?)

原因是,有一些内置程序是计算机自带的,还会附带一些面向终端的应用程序,这些应用程序,存储在你的文件系统中,Shell 有一种方法可以确定程序的位置,通过使用环境变量。

环境变量

环境变量是在启动 Shell 时设置的东西,它们并不是你每次运行 Shell 时都必须设置的东西。

有一些已经被设置好的变量,像是你的主目录在哪里,你的用户名是什么...

要查看搜索路径,有一个至关重要的变量,即路径变量

  • $PATH 路径变量

    同样我们可以使用 echo 将其打印

    1
    2
    ekko@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
    2
    ekko@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
2
ekko@Ling:~/桌面$ pwd
/home/ekko/桌面

所有的相对路径都是相对于当前工作目录而言的,同样的,我也可以改变我的当前工作目录,通过使用 cd 命令(change directory)

1
2
ekko@Ling:~/桌面$ cd /home
ekko@Ling:/home$

同时也有几个特殊的目录存在

  • . 表示当前目录

  • .. 表示父目录

  • 这也是一个快速切换工作目录的方式

    1
    2
    ekko@Ling:~/桌面$ cd ..
    ekko@Ling:~$

    快速回到上一级目录

  • 也可以通过相对路径,切换工作目录

    1
    2
    ekko@Ling:/$ cd ./home
    ekko@Ling:/home$

    切换到当前目录下的home目录

  • 当然如果切换到没有的目录,他也会给出没有的提示

    1
    2
    ekko@Ling:/home$ cd ./home
    bash: cd: ./home: No such file or directory

通过构建路径,来遍历文件系统的方法

一般情况下,我们可以任意使用相对路径和绝对路径,使用两者中最简单的

而如果你编写了一个程序,想要在任何情况下都能运行,那么应该给出绝对路径,因为如果给出相对路径,在工作目录切换时,会出现无法成功运行的现象

通常当我们运行程序时,它默认会在当前工作目录下进行操作,除非给出参数,这非常方便,意味着我们通常不需要提供完整路径,我们只需要使用文件名和当前所在的目录。

  • 有些时候我们想要知道当前工作目录下有哪些文件,这时候就需要 ls 命令

    1
    2
    3
    4
    ekko@Ling:/$ ls
    bin dev lib libx32 mnt root
    boot etc lib32 lost+found opt run
    cdrom home lib64 media proc sbin

    快速浏览当前位置文件

  • 或者 ls {路径} 将参数作为路径传入,可以得到对应路径下的所有文件

    1
    2
    ekko@Ling:/$ ls /home
    ekko
  • ~ 总是拓展到主目录,所以可以使用 ~ 来写相对路径

    1
    2
    3
    ekko@Ling:/$ cd ~
    ekko@Ling:~$ pwd
    /home/ekko
  • - 切换到之前所在的目录

    1
    2
    3
    4
    ekko@Ling:~$ cd -
    /
    ekko@Ling:/$ cd -
    /home/ekko

对于程序的参数,大多数程序会采用所谓的参数,如标志和选项,这些标志和选项通常以 - 开头

  • --help 大多数程序都实现了这个选项

    1
    2
    ekko@Ling:~$ ls --help
    打印出大量该命令的帮助信息

    你将看到程序的功能描述,和各种可用的标志和选项的详细说明

    通常我们称 -{单个字母} (单个破折号和单个字母的组合)为标志(flag),不带任何值的内容也是标志,带有值的东西为选项(option)

    例如:-a--all 都是标志,-C--color 是选项

  • -l ls 命令加该参数,可以提供很多非常有用的额外信息

    1
    2
    3
    4
    5
    ekko@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 命令进入某个目录,你必须拥有该目录及其所有父目录的执行权限

      假如,你对一个文件有写入权限,但是对文件所在目录没有写入权限,那么你可以清空这个文件,但是不能删除它,因为清空文件,相当于修改文件,与文件的写入权限有关;而删除文件,是与文件所在目录的写入权限有关

    • 还有 stl (查资料)

  • 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
    2
    ekko@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 的大部分强大功能就会真正体现出来,而不只是简单的 cdls,你还可能希望与文件交互,并让文件在不同程序之间传输。这时,我们可以使用 Shell 提供的的概念来实现。

流 stream

每个程序都有两个主要的流:

  • 输入流 input stream
  • 输出流 output stream

默认情况下,输入流来自于键盘,基本上是你的终端,你在终端中输入的任何内容都会进入程序;输出流则是每当程序打印输出时,输出的内容就会传递到这个流中。默认情况下,输出流也是指向你的终端的。

Shell 提供了一种重定向这些流的方法,以改变程序的输入/输出指向。

  • 最简单的方法是使用尖括号符号 <>

    < 表示将这个程序的输入重定向为这个文件的内容

    > 表示将前面程序的输出重定向到这个文件中

    1
    ekko@Ling:~/桌面$ echo hello > hello.txt

    没有重定向前,将会在回车后命令下方打印“hello”,而重定向后“hello”被打印到 hello.txt 文件中,可以通过 cat 命令验证

    cat 命令还有一种功能,支持将输入与输出连在一起

    1
    2
    ekko@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
    2
    ekko@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
    2
    ekko@Ling:~/桌面$ ls -l / | tail -n1
    drwxr-xr-x 14 root root 4096 2月 23 2022 var

    注意,这里,ls 不知道 tailtail 也不知道 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 的文件系统。可以通过

image-20230620204350428

如果你 cd 到这里,你会发现很多并不是你计算机上的文件,而是各种内核参数,内核就像是你的计算机的核心。你可以通过文件来访问各种内核参数。

如果继续进入 “class” 他有许多不同类型的设备可以与之交互,或者可以访问各种队列或各种内部奇怪的旋钮。

image-20230620204418479

由于他们暴露为文件,这意味着我们可以使用到目前为止学到的所有工具来操作他们。

在操作过程中,如果直接利用重定向修改文件,操作会被拒绝

1
2
ekko@Ling:/sys/class/backlight/intel_backlight$ echo 500 > brightness
bash: brightness: Permission denied

因为如果要修改内核,是需要根用户的权限的

可以想到的一个解决办法是利用 “sudo”

1
2
ekko@Ling:/sys/class/backlight/intel_backlight$ sudo echo 500 > brightness
bash: brightness: Permission denied

但是仍然会被拒绝,为什么呢?

就是之前强调的,输入和输出的重定向不是程序所知道的,当我们将 “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
    3
    ekko@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
    2
    ekko@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
    2
    ekko@Ling:/tmp/missing$ ls ./semester -l
    -rw-rw-r-- 1 ekko ekko 61 6月 22 20:53 ./semester

    文件拥有者只有读写权限,无执行权限

  • 使用 sh semester 运行

    1
    2
    ekko@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
    22
    ekko@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
    5
    ekko@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
    2
    ekko@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
    25
    ekko@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
    2
    ekko@Ling :/tmp$ ./semester | head -n 4 | tail -n 1
    last-modified: Sat, 10 Jun 2023 13:13:30 GMT