本教程初次编写于六星2023CTF夏令营,升级后作为Linux系列教程的第零篇。
欢迎对Linux毫无了解的读者通过本教程入门Linux命令行基础操作~

What is Shell?

Shell的中文是壳,读者可能听说过一部叫做《Ghost in The Shell》(攻壳机动队)的作品。

Ghost-in-the-Shell

在计算机的世界中,Shell指的是一类软件,允许用户和计算机进行交互。

In computing, a shell is a computer program that exposes an operating system‘s services to a human user or other programs. In general, operating system shells use either a command-line interface (CLI) or graphical user interface (GUI), depending on a computer’s role and particular operation. It is named a shell because it is the outermost layer around the operating system.

—WikiPedia

在远古时期,我们没有鼠标,也没有图形化界面。那时候,我们就使用键盘来给计算机发送信息,而计算机就通过只能显示文字的显示屏来给我们显示信息。(当然,更远古的时期我们有的只是打孔纸带)这个能够读取我们命令,从而允许我们操作电脑、打开程序、移动文件的程序,就叫做Shell。这是计算机的Shell,也是我们自己在宽广的网络世界中的Shell。

在现在,Shell,尤其是 CLI Shell(Command-Line Interface Shell,命令行shell),依然在计算机世界中发光发热,为广大CSer提供着基础、方便、强大的与计算机的交互手段。尽管我们已经有了简洁直观的GUI(Graphical User Interface,图形用户界面)、智能的语音输入、甚至酷炫的Apple Vision Pro等AR/VR设备,但这些交互方式的强大之处也正是其缺陷——它们限制了我们的自由,让我们只能按照给定的接口与设备进行交互。

这个教程将会已Kali系统为例(当然其他发行版也适用本教程),讲解Linux系统的shell基础。

使用shell

所有的Linux系统都会预装一个Shell,而这些Shell的语法都是类似的,差别大部分在于一些拓展功能。比如在Ubuntu等系统上,默认的shell是 bash,而Kali系统上,预装的Shell叫做 zsh

当你打开你的Shell程序,你会看到一个提示符(prompt),表示 请您输入指令,比如:

1
2
┌──(kali㉿kali)-[~]
└─$

本教程之后会使用 $ 代指这个提示符。

在shell程序的可执行文件中,其实是一系列字符串处理的代码,负责将我们输入的字符串解析成指令来执行。比如,我们可以输入data:

1
2
$ date
Fri 10 Jan 2020 11:49:31 AM EST

shell 解析我们输入的字符串 "date" ,发现用户想要获取时间,于是就打印出了当前时间。

我们再试试输入 echo hello

1
2
$ echo hello
hello

shell 解析我们输入的字符串 "echo hello" ,根据空格来分割出两个token:echo 以及 token

然后 shell 将第一个token解析成指令,后面token都解析成参数。它发现我们是要求回声(echo),于是就调用相应的代码,将我们传给它的第二个token hello 打给了我们。

那我们都有哪些可以用的指令呢?我们可以用的指令大概有两种:

  • shell自己代码实现的指令
  • 计算机上的程序

如果shell发现我们输入的指令并不是它自己实现的,它就会认为这是一个计算机上的程序。如果它在计算机上找不到这个程序的话,就会报错:

1
2
$ Genshin start!
Genshin: command not found

Where am I?:文件系统

我们知道,计算机里的文件一般是以文件夹的形式组织起来的。在Windows下,我们可以看到C盘这个”大文件夹“下面有着 Program Files, Users 等等文件夹,在其中又有着许多许多的文件夹……

如果我们把C盘当作树根,文件夹当作树枝,文件当作树叶,我们可以发现这不就是一棵树吗!在Linux文件系统中,真的有这么一个目录,就叫做根目录(root directory),其符号为 /

想要看看Linux的文件树上都有哪些枝叶吗?让我们先走到树根那里:

1
2
3
4
┌──(kali㉿kali)-[~]
└─$ cd /
┌──(kali㉿kali)-[/]
└─$

我们可以使用 cd 指令,其全称是 change directory。在shell中输入 cd / 之后,我们会发现prompt有一定的变化。(这很像MUD,一种的远古文字游戏)

有一个 ~ 符号变成了 /,这表示我们的位置已经到了树根这里。这里有什么呢?

1
2
3
4
5
6
┌──(kali㉿kali)-[/]
└─$ ls
bin home lib32 media root swapfile var
boot initrd.img lib64 mnt run sys vmlinuz
dev initrd.img.old libx32 opt sbin tmp vmlinuz.old
etc lib lost+found proc srv usr

我们可以看到这里有很多东西,这是不同Linux发行版都会有的约定,也就是什么东西放在哪里。接下来我尝试用cd和ls结合回到家目录。

1
2
3
4
5
6
7
8
9
10
11
12
┌──(kali㉿kali)-[/]
└─$ cd home

┌──(kali㉿kali)-[/home]
└─$ ls
kali

┌──(kali㉿kali)-[/home]
└─$ cd kali

┌──(kali㉿kali)-[~]
└─$

我们发现,在prompt上显示的位置又回到了 ~

我们进入(回到)这个文件夹时,顺序依次是 / home kali,因此可以把这个拼接成其绝对地址 /home/kali

让我们输入 pwd (print working directory)来确认一下,这个指令能够告诉我们现在我们在哪:

1
2
3
┌──(kali㉿kali)-[~]
└─$ pwd
/home/kali

实际上,~ 这个特殊符号就表示当前用户(kali)的家目录。我们可以把这里当作桌面一样的地方来保存文件。

常用的文件系统指令包括:

1
2
3
4
5
6
7
8
$ pwd    # 我在哪?
$ ls # 这里有什么?
$ cd xxx # 我要去xxx文件夹
$ cd .. # 我要去上一级文件夹
$ mv # 移动文件
$ cp # 复制文件
$ rm xxx # 删除文件 (文件夹要加上-r参数)
$ mkdir # 创建文件夹

文件读写:配置软件安装源(apt包管理器)

文件夹中,不仅有着目录,还有着其他的文件。接下来我们以配置软件安装源为例,展示文件相关的操作。

在Linux上,有着被称为软件包管理器的一类软件,负责统一管理系统上软件或库的安装、升级、卸载,类似苹果的App Store。由于这是统一管理的,所以安装软件或库会非常方便。

通常而言,在国内安装Linux之后,首先就是需要更改所使用的软件包管理器(Ubuntu和Kali都使用apt)的软件仓库地址(或者称为软件源)。由于某种神秘力量的影响,国内可能不能访问官方默认的软件源(或很慢),因此国内有许多组织都维护了镜像站(也就是官方源的国内镜像版本)。

比如说,可以使用由 USTC LUG(中科大 Linux User Group)维护的镜像服务,官方教程的链接是:发行版镜像使用帮助

在上面这个教程中,可能写了这个:

编辑 /etc/apt/sources.list 文件, 在文件最前面添加以下条目:……

运用上面的知识,我们已经可以移动到 /etc/apt/ 文件夹,并且用 ls 看到这个目录下确实有一个 sources.list 文件。

我们可以使用 cat 指令来查看这个文件有什么内容,只需要在 cat 后面加上这个文件的路径。由于这个文件就在当前文件夹,所以只需要输入文件名即可,这是一种相对路径。你可以使用tab键来让shell帮你自动补全文件名,减少输入的功夫。

1
2
3
4
5
6
7
┌──(kali㉿kali)-[/etc/apt]
└─$ cat sources.list
# See https://www.kali.org/docs/general-use/kali-linux-sources-list-repositories/
deb http://http.kali.org/kali kali-rolling main contrib non-free non-free-firmware

# Additional line for source packages
# deb-src http://http.kali.org/kali kali-rolling main contrib non-free non-free-firmware

现在这个文件里已经有这么几行链接了,接下来我们尝试使用 nano 编辑器来对其进行编辑,还是和上次一样,只需要在 nano 后面加上文件名(也就是用相对路径引用这个文件):

$ nano sources.list

打开后,我们会在底下发现一行红色报错:File 'sources.list' is unwritable

Linux用户、用户组、权限

真的unwritable吗?如wri。

Linux系统给许多重要的文件、目录都设置了权限要求,没有权限的普通用户没办法修改。(是的,默认的用户kali是普通用户)在Linux系统中,有一个特殊的用户叫做root,几乎拥有一切权限,但为了这台计算机系统的安全起见,我们一般都不会登录这个用户。

试想一下,当你一不小心在命令行上敲出了 rm -rf / (对整个文件系统进行删除),如果你这时候还是普通用户的话,系统会提示你权限不够,从而保护了自己。但如果你是root用户的话……和这台系统说再见吧~

我们可以用 ls -l 来查看当前目录下各种文件的详细信息,其中各列信息如下所示:

文件属性 文件数 拥有者 所属group 文件大小 创建时间 文件名
drwxr-xr-x 2 root root 4096 Jun 3 13:38 apt.conf.d
-rw-r–r– 1 root root 1033 Jan 1 10:12 sources.list

文件属性一共有十个字母,第0个表示文件类型,d指directory,-指普通文件。

13,46,7~9分别为一组,分别表示文件所有者、所属group用户、其他用户对文件的权限。r表示可读(readable),w表示可写(writable),x表示可执行(executable)。

我们观察sources.list的文件属性,可以发现root用户可以进行读写,而root group的用户和其他用户都只读。

因此,为了修改这个文件,我们必须要使用root用户的权限。好在这有一种简单的方法可以做到:在想要执行的指令之前加上 sudo。因此我们还是可以写的。

更多细节可以参考本系列的下一篇教程,不过下一篇教程难度距离本篇高了一截,推荐已经使用一段时间Linux、对计算机整体架构有基本认识的读者阅读。

编辑文件

我们观察nano底部的操作栏,可以找到左下角的Exit。这里的 ^X 表示Ctrl+X的意思。因此我们使用这个组合键来退出。退出以后,我们按 ↑ 键即可取出上次我们执行的指令,然后再敲 Home 键(数字键盘附近的)将光标提到最前面,输入 sudo空格,敲击enter。

$ sudo nano sources.list

这里会提示我们输入密码,如果是kali系统就输入默认密码 kali

进入nano编辑器的界面后,复制中科大源的两个链接,将光标移到开头之后使用 Ctrl+Shift+V 来将其黏贴上去。这个编辑器的逻辑和我们常用的记事本等差不多,所以大家可以自行修改。

修改完成以后,先使用 Ctrl+S 进行保存,然后再 Ctrl+X 退出即可。

当然你也可以用教程中的其他方法完成换源

感觉如何?命令行的编辑器也可以非常强大,不论是功能性、美观性还是方便性的角度都是如此。有一款各种Linux都会自带的编辑器被许多人称为”编辑器之神“,它就是大名鼎鼎的Vim。如果你有兴趣,可以在 编辑器 (Vim) · the missing semester 进行了解。

好吧我知道这个很有用,但是这个指令咋用啊?:tldr

在计算机的世界中,有着许许多多的奇怪缩写,接下来介绍的这个软件就有一个非常奇怪的缩写:tldr

too long don’t read: 太长不看

接下来我们将会展示如何安装这个软件。在安装软件之前,先更新一下本地的资源目录是一个很好的习惯:

$ sudo apt update

它会提示你有多少个包可以更新,我们可以选择先别管他,因为刚刚安装系统后更新可能要很久(我这里有781个包要更新)。我们输入:

$ sudo apt install tldr

经过一些提示信息,当我们再次看到prompt的时候,就说明这个软件已经装好了。我们可以试着运行一下它,来看看到底是不是装好了:

1
2
3
4
5
6
7
8
9
10
11
12
$ tldr
tealdeer 1.5.0
Danilo Bargen <mail@dbrgn.ch>, Niklas Mohrin <dev@niklasmohrin.de>
A fast TLDR client

USAGE:
tldr [OPTIONS] [COMMAND]...

ARGS:
<COMMAND>... The command to show (e.g. `tar` or `git log`)

......

接下来,我们还需要更新一下这个软件的本地缓存(可能会有些慢):

1
2
$ tldr --update  # 你也可以用-u来代替--update
Successfully updated cache.

在这之后,我们就可以愉快地使用这个软件了!让我们用一个经典的递归式查询来查询一下tldr的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ tldr tldr    

Display simple help pages for command-line tools from the tldr-pages project.
More information: <https://tldr.sh>.

Print the tldr page for a specific command (hint: this is how you got here!):

tldr command

Print the tldr page for a specific subcommand:

tldr command-subcommand

Print the tldr page for a command for a specific [p]latform:

tldr -p android|linux|osx|sunos|windows command

[u]pdate the local cache of tldr pages:

tldr -u

想知道什么指令的用法,或者不记得哪个指令怎么用了,直接使用 tldr <命令名> 就能快速地查到这个软件常用的用法了。

作为习题,你可以挑战一下快速找到如何使用apt来列出本机安装的软件包、如何将本机的软件包升级到最新的版本、以及如何删除一个软件包(删除时请一定小心!初学时笔者曾尝试删除python2,导致系统里许多依赖python的软件都无法正常运行,桌面软件都爆炸了,花了好久才给救回来)。

查询非常用用法:Manual手册

我们使用tldr查询用法时,实际上是一种”非官方渠道“,也就是由非官方的组织收集得来的常用用法。这其实并不能保证查到的用法一定是正确的、我们想要的,也不能帮助我们将一个软件的功能发挥到极致。

如何查询一个指令/软件的官方用法?答案是使用 man 指令,其全称是 manual (手册)。基本上每个软件或指令都会有对应的、由官方编写的、规范化的文档,我们通过man来阅读它们。如果没有对应的 man page 的话,这表示这个指令可能是由shell程序自己实现的而不是一个独立的软件,这时候可以通过 help xxx 来查看其用法。

这里又引出了另一个非常奇怪的缩写:RTFM,以下是两种解释(看你想如何理解)

Read the F**king Manual:快™去读手册!!

Read the Friendly Manual:去看看友好的手册吧~

早年间,很多伸手党小白会在网络上提非常简单的问题,这时候懒得回答的暴躁老哥就会说出这句经典缩写。这个小故事启发我们:当遇到不懂的事情,多查查手册这一官方出品的一手资料,慢慢地你一定会变得更有耐心、更能解决问题、英语阅读分更高

另外,在man的页面当中使用 / 可以在当前文档中向后搜索关键词,使用 n 来切换到下一个搜索结果,使用 shift+n (就是大写N)来切换到上一个搜索结果。这个功能会很有用。

让我们做个小练习,请借助 man ls ,找到能做到以下效果的指令:

  • 打印隐藏的文件
  • 大小以人类可以理解的格式显示(比如显示 454M 而不是 454279954)
  • 文件以最近访问顺序排序

我是MARIO:iostream重定向 & pipes管道

上过C语言和C++的大家应该知道,命令行程序有三个默认打开的“流”,分别是stdin,stdout以及stderr。在我们运行命令行程序时,标准输入就是我们敲进去的东西,而程序打印东西到stdout或者stderr,其实就是打印到命令行上。

在学习C或C++的文件操作的时候,会发现一个很巧的事情——文件读写用到的API,和标准输入输出用的那些API其实都差不多,这是因为文件和标准输入输出本来就是一样的。在Linux系统中,一切皆文件。不仅传统意义上的文本文件、多媒体文件等普通文件是文件,套接字(网络接口)、键盘鼠标设备等等都是文件,可以用统一的一套文件API进行处理。

这种设计不仅带来了极大的统一性,也带来了极大的便捷性。本节介绍的iostream重定向和pipes管道就与这种设计有关。

既然stdin和stdout也是文件流,那么我们当然可以把他们重定向到一个普通文件!我们让一个文件被定向到一个程序的标准输入,或者让一个程序的标准输出定向到一个文件当中。前者我们使用 < file,后者我们使用 > file,如下所示:

1
2
3
4
5
6
7
8
9
10
$ echo hello > hello.txt		# 把stdout重定向到hello.txt中
$ cat hello.txt
hello

$ cat < hello.txt # 把hello.txt重定向到cat的标准输入
hello

$ cat < hello.txt > hello2.txt # 同时重定向cat的标准输入和标准输出
$ cat hello2.txt
hello

那我们能不能把两个程序首位相连呢?把第一个程序的标准输出用作第二个程序的标准输入,在这两个程序之间构造一条虚拟的管道!这种神奇的操作是通过 | 来完成的:

1
2
3
4
5
6
7
8
9
$ cat 1.txt           
GODEL
ESCHER
BACH
$ cat 1.txt | tail -n1 # 打印1.txt的最后一行
BACH
$ cat 1.txt | grep CH # 寻找1.txt中带有CH的行
ESCHER
BACH

据说,传奇的命令行大师会用管道构造一长条链,我们尊称其为Mario。

Play with Shell

接下来,笔者推荐有空的读者试试看一个Wargame,链接是OverTheWire: Bandit。这个闯关游戏能帮助初学者快速掌握各种命令行常用工具的用法。在有了本教程的基础以后,这个游戏仍然有一定的挑战性,需要广泛地收集资料。

在这里,最后介绍一个奇怪的缩写:STFW

Search The Friendly Web: 去搜搜互联网吧,相信你能找到想要的

Remember - this stuff is not easy if you don’t know much, so google everything, question everything, and sooner or later you’ll be down the rabbit hole far enough to be enjoying yourself.

— How to start hacking? - r/hacking

祝愿大家能通过这个Wargame,从此发现一个不一样的计算机世界。


参考资料

强烈推荐:计算机教育中缺失的一课 · the missing semester