Linux 教程新手篇(草稿)

Linux 教程新手篇(草稿)

November 16, 2024

在做实验,需要每五分钟去调整一下仪器,但中间的五分钟有点无事可做。 所以来写一下这个东西得了。

原本是想要写个给同专业的同学看的教程,写了一点觉得,诶,我为啥不写成更通用的教程呢。 于是就这样做吧。

顶层设计

我的想法是这样的:按照两个部分去写,一个新手篇,一个进阶篇。 不管哪个部分,内容都不会限制于 Linux,甚至 Linux 的内容可能只占一半。 其实很多人并不是不会用 Linux 而是对缺少对操作系统的一般认识, 就像看到自己电脑有 C D E F 盘,就以为电脑里真的插了四个硬盘一样。 另一个常见的误区就是不区分内存和外存。类似此类的问题。 要真的只是某个 Linux 上的问题不会,反而还都是少见的。

暂时的计划是这样的,新手篇的内容包含:

  • 什么是 shell?(和 windows 的桌面一个作用,只是变成了敲命令)
  • shell 里命令的格式是怎样的?(第一个是要执行的程序,之后的是参数)
  • 常用的几个命令。 说白了就是围绕 bash 介绍一些基础用法。 这其实是相当繁琐的事情,但又是很基础的,不得不介绍,新手接触 Linux 的话也不可能绕过。

进阶篇的内容会多一些杂一些,包括:

  • 冯诺依曼架构(计算机的硬件组成,至少把内存和外存分清楚吧)
  • 操作系统的结构(内核和用户态程序的区别)
  • 分区、文件系统、挂载,这些概念是指什么;linux常见的几个文件夹里放什么。
  • 程序和进程间通信(环境变量,信号,输入输出,返回值)
  • shell 中一些内置命令,以及重定向、通配符等。
  • 常用的工具(shell 中常用的外置命令)。
  • 用户、文件所有者和权限。
  • 网络协议,NAT。
  • 编程时常用的概念(编译、链接、解释器等)。

在这之后就区分“专业”了,例如做科学计算的话,需要了解:

  • 常用的并行计算协议(MPI,OpenMP,CUDA,OpenCL,SYCL)。这里其实我也缺一些知识,我需要自己先把自己的疑惑解决了再写。
  • 队列系统。但是我已经写过了。

当然也可以写一些其它的。但这些要么已经有人写过了写得很好我受益匪浅,我觉得不需要我来写(例如 nix),要么我自己也不熟(例如 docker)。 总之,写个屁。

我想要达成的目标是这样的:

  • 对于普通用户:
    • 读完新手篇,就可以着手做计算了。
    • 读完进阶篇,就大致知道自己遇到常见的错误时如何排查,或者如何向别人描述问题了。
  • 对于管理员:
    • 全读完,就可以开始学习怎么搭服务器了(遇到问题时,知道去哪里找答案了)。

不只是做计算,如果有想自己折腾 Linux 的,这也算是一个比较好的入门;但我会侧重于前者,主要是后者的话,场景太杂,我根本不知道该写啥。

所以说应该会写四个部分:

  • 速查篇,列个表格,满足某些人“我要一口吞个大象”的心态,他吞不下是他的问题,我给他把大象放到这里。
  • 新手篇,口语化地带领读者实践一圈。
  • 进阶篇,讲基础的、常见的、重要的、但是又往往被忽略的概念,为了给进一步向前走做准备。
  • 专业篇,指科学计算,这一部分更偏向于我自己的整理而不是科普给别人。

那就开始吧

下面是新手篇。

那个黑框框里是什么?

先考虑这个问题:在 Windows 电脑上,如何删除一个文件?步骤是这样的:

  1. 启动资源管理器(就是“我的电脑”或者“此计算机”)。
  2. 打开你要删除的文件所在的文件夹。
  3. 右键点击文件,选择删除。 在这个过程中,“资源管理器”或者“exploer.exe”承担了这样一个“传话筒”的角色: 它接受你下达的指令(打开某个文件夹,删除某个文件,等),并将这个指令传达给系统中那些更底层的程序(即内核)去执行。

通过 PuTTY 或者别的什么程序连接到服务器后打开的那个黑框框,就是 Linux 系统中的“资源管理器”[^2],它的名字叫“bash”[^3]。 它的作用与 Windows 的资源管理器是一样的:你可以给他传达删除某个文件、启动某个程序等指令,它就会把这个指令传达给系统的更底层去实际执行。 学习 Linux 的第一步,就是学习如何给 bash 下达指令,例如打开某个文件夹、删除某个文件、启动某个程序等。 至于其中的细节(例如 bash 是如何与内核交互的,为了删除一个文件内核又需要做什么),暂时不需要关心。

类似于 Windows 的资源管理器或 bash 这样的程序被称为“shell”,也就是“壳”, 这是指它把系统中复杂的细节包装了起来,让用户可以简单地进行删除文件、打开文件夹等操作,大多情况下不用关心细节。

几个常见的命令

学习英语的第一步并不是钻研语法,而是学习最常用的几句话大致怎么说。学习 bash 也是一样的。 下面是几个最常用的命令,你可以跟着操作一遍,就大致知道这些命令该怎么用了,同时也可以理解 Linux 上一些基本的概念。

⚠️
如果你是在学校超算(jykang)上跟着操作,那么一定要小心,每一次回车前都要仔细检查输入的命令是否正确,千万不要误操作。

pwd

在 Windows 的资源管理器中,可以在窗口上部、标题栏下方的地址栏中看到现在打开的是哪个文件夹(称为当前的“工作目录”); 而在 bash 中,可以使用 pwd 命令查看工作目录的路径(pwd 是“print working directory”的缩写):

pwd

pwd 输入到黑框框中,然后按回车键,它就会输出一行文字,告诉你现在在哪个文件夹下。例如,它输出的结果可能是:

/home/chn

这表示你现在打开的是“根目录”下的 home 文件夹下的 chn 文件夹。

要解释“根目录”的意思,需要先提到一个 Linux 与 Windows 的不同之处: Windows 中往往逻辑上有多个“磁盘”(C 盘、D 盘、E 盘等),而 Linux 中逻辑上只有一个“磁盘”,这个“磁盘”就是所谓的“根目录”。 当然,这个“根目录”中的内容完全可能实际是分布在多个硬盘上存储的(例如,某几个文件夹在一个硬盘上,另外几个文件夹在另一个硬盘上), 但这些细节初学者并不需要关心,这些文件实际上存储在哪里对初学者来说操作起来完全没有区别。

你还会发现另外一个区别:在 Windows 中,文件夹之间的分隔符是反斜杠 \,而在 Linux 中,文件夹之间的分隔符是斜杠 /

还有第三个重要的区别你可能没有发现: 在 Windows 中,文件和文件夹名字是不区分大小写的,也就是说无法在同一个文件夹下新建 aaa.txtAAA.txt 两个文件; 而 Linux 中文件和文件夹名是区分大小写的,aaa.txtAAA.txt 完全可以同时存在。 不仅仅是文件名,整个 Windows 的习惯都是不区分大小写的,而整个 Linux 的习惯则都是区分大小写的。 总之,我们之后学习的所有命令、各种参数等,它们都是区分大小写的; 如果你在哪里看到过某某命令不区分大小写的说法,那大概率是 Windows 的特色,不能不经确认套用到 Linux 上。

ls

使用 ls 命令(“list”的缩写)列出当前目录下,名字不以 . 开头的文件和文件夹(也就是不隐藏的文件和文件夹):

ls

在 Windows 上,一个文件或者文件夹是否是隐藏的,是它的一个属性(右击-属性中,可以修改); 但在 Linux 上,通常直接把名字以 . 开头的文件或文件夹称为“隐藏文件”或“隐藏文件夹”,并通常用来存放程序的设置等用户平时不需要修改的内容。 隐藏的文件与不隐藏的文件本质上并没有什么不同,这只是一种习惯性的用法。

要列出当前目录中所有的文件和文件夹(包括隐藏的),可以使用 ls -a 命令(“a”是“all”的缩写):

ls -a

你一定可以看到两个特殊的文件夹:一个名字叫 .,令一个叫 ..。我稍后会介绍它们是什么。

要列出别的地方的文件夹里的内容(而不是当前文件夹),可以在 ls 命令后面加上这个文件夹的路径,例如:

ls /

这会列出根目录下的文件和文件夹(不包括隐藏的)。

ls /home/chn

这会列出 home 文件夹下的 chn 文件夹中的文件和文件夹(不包括隐藏的)。 当然,如果你没有权限看这个文件夹里的内容的话,这个命令就会报错,但也不会有什么坏处,也不会影响之后的命令。

你可以按照自己的兴趣,随意列出几个文件夹的内容,探索一下服务器上有什么东西。不会把服务器搞坏的,不用担心。 也可以将 ls 列出来的东西与 WinSCP 对照者看一下,确认它们是一样的[^4]。

ℹ️
如果你尝试列出 /nix/store 或者 .node_modules 的文件夹的内容,你的 shell 可能会卡死,这只是因为里面的文件太多了。 你可以耐心等待它列完,也可以按 Ctrl + C 来终止这个命令,实在不行就重启 PuTTY。

mkdir

使用 mkdir 命令(“make directory”的缩写)在当前目录下创建一个新的文件夹:

mkdir test

这样就在当前目录下创建了一个名为 test 的文件夹。

cd

使用 cd 命令(“change directory”的缩写)切换当前目录(也就是,打开别的文件夹):

cd test

这样就进入了当前目录下的 test 文件夹。

如果要返回上一级目录,可以使用 cd .. 命令:

cd ..

还记得前面提到的 ... 这两个特殊的文件夹吗?其实 .. 就是指上一层文件夹,而 . 就是指同一层文件夹。 例如,下面几个目录的写法,它们之间是完全等价的:

/home/chn
/home/chn/.
/home/chn/./././././.
/home/../home/chn
/home/chn/../../home/chn
/home/././chn/.././chn

目前为止,很多命令都需要用到文件或文件夹的路径。 实际上,文件或者文件夹的路径有两种写法,大多数情况下(包括基础篇中提到的所有命令但不仅限于),两种写法都可以,完全等价:

  • 直接写当前目录下的文件或文件夹的名字,例如 cd test 或者 cd ./test 或者 cd ./test/../test 等,这种写法称为相对路径;
  • 写完整的路径(以 / 开头的路径),例如 cd /home/chn/test,这种写法称为绝对路径。 但也存在一些例外,其中一个重要的例外在基础篇的最后会提到。

echo

使用 echo 命令输出一行文字:

echo Hello

这样就输出了一行 Hello。——这有什么用呢?

可以使用下面这个命令,在当前目录下创建一个名为 file.txt 的文件,并在这个文件中写入一行 Hello (如果这个文件之前已经存在,其中的内容会被覆盖):

echo Hello > file.txt

如果你想在这个文件中追加一行 World(之前的内容不会被覆盖),可以使用下面这个命令:

echo World >> file.txt

这里的 >>> 是两个特殊的符号,被称为“重定向符号”, 它表示把之前的命令的标准输出(也就是,原本会输出到屏幕上的内容[^5])重定向到某个文件中。 > 表示覆盖,>> 表示追加。 它们的用法不仅限于 echo 命令,而是可以用在任意的命令上,例如 ls > file.txt 就会把 ls 命令的输出重定向到 file.txt 文件中。

cat

使用 cat 命令(“meow meow meow”的缩写[^6])查看一个文件的内容:

cat file.txt

你当然可以“活学活用”,把 cat 命令的输出重定向到另一个文件中,这样就变相实现了文件的复制:

cat file.txt > file2.txt

类似于这样的“活学活用”在 Linux 中其实比比皆是。就像一篇光鲜亮丽的 sci 论文背后总是充满了科研现实的苟且。

cp

这个才是正经用来复制文件的命令(copy 的缩写):

cp file.txt file2.txt

如果要复制的是文件夹,需要加上 -r 参数(“r”是“recursive”的缩写[^7]):

cp -r test test2

rm

使用 rm 命令(“remove”的缩写)删除一个文件:

rm file.txt

这样就删除了当前目录下的 file.txt 文件。

要删除文件夹,需要加上 -r 参数(与 rm 相同):

rm -r test

这样就删除了当前目录下的 test 文件夹。

bash 的基本语法

按行分割

英语分为一个个句子,以句号作为结尾;输入给 bash 的指令是一条一条的,通常以换行作为结尾。 例如,输入下面的命令:

cd test
rm file.txt
cd ..

bash 会把它作为三条指令来执行。

命令与参数

英语的一句话中,主语后接的是谓语、宾语; bash 的一条指令中,按照空格分隔,第一个单词是要执行的命令,之后如果还有内容的话,只要不是一些特殊的符号(例如 >),就是这个命令的参数。 例如在下面的命令中:

rm file.txt

rm 是要执行的命令,file.txt 是这个命令的参数。

在按照空格分割的时候,连续的多个空格与单个空格是等价的,例如下面的命令:

rm    file.txt

与上面的命令是等价的。

如果命令或者参数中包含空格,可以用双引号或单引号把它括起来,例如:

rm "file with space.txt"

这样 bash 就知道 file with space.txt 是传给 rm 的一个参数;而不是把它分成 filewithspace.txt 三个参数。 也就是说,要删除的是名为 file with space.txt 这一个文件,而不是名为 filewithspace.txt 的三个文件。

每个命令可以接受参数的个数不同。 例如,删除文件的命令通常需要至少一个参数(你得告诉它删除哪个文件),至多则不限制; 而复制文件的命令通常至少需要两个参数(你得告诉它将哪个文件复制到哪里)。

内置命令与外部程序

cdpwd 等命令并不会启动 bash 以外的程序:它们是 bash 内置的命令,它们的作用是调整或者输出 bash 本身的状态, 相当于查看或者修改 Windows 的资源管理器本身的设置,而不是要 bash 做什么实际的事情。 而 rm file.txt 这个命令则实际上是启动了一个放置在特定位置[^8]、名为 rm 的程序,并将 file.txt 作为参数传给它。 mkdircp 等命令也是这样的。

那么,如果要运行一个程序,而这个程序又不放在类似于 /usr/bin 这样的特殊位置,应该怎么调用呢?有三个办法:

  • 修改环境变量。新手篇不会介绍这个办法,因为新手很容易把环境变量搞乱。
  • 使用绝对路径调用程序。例如,这个程序放置在 /home/chn 文件夹下,名字叫 myprogram,那么可以这样调用:
    /home/chn/myprogram
    如果要传递参数给这个程序,也是一样的写法:
    /home/chn/myprogram arg1 arg2
  • 使用以 . 开头的相对路径调用程序。必须使用以 . 开头的相对路径(.. 也可),这就是之前提到的那个重要的例外。 例如,这个程序放置在当前文件夹下,名字叫 myprogram,那么可以这样调用:
    ./myprogram

管道与 grep 命令

除了之前介绍的 >>> 之外,还有一个非常常用的特殊符号,叫做“管道符号” |,就是键盘上那个竖线。 它的作用是把一个命令的标准输出重定向到另一个命令的“标准输入”(就是这个命令原本看到的用户的输入[^9])。

管道符号经常和 grep 命令一起使用,grep 命令的作用是在输入中查找某个字符串,并输出包含这个字符串的行。 例如:

cat OUTCAR | grep "Total energy"

这个命令可以输出 OUTCAR 文件中包含 Total energy 的行。

grep 有一些常用的参数,包括:

  • -i(“ignore case”的缩写):匹配时忽略大小写;
  • -An(“after”的缩写): 除了输出匹配的内容,还输出匹配内容的后几行;
  • -Bn(“before”的缩写): 除了输出匹配的内容,还输出匹配内容的前几行;
  • -v(“invert match”的缩写):输出不匹配的行,而不是输出匹配的行。

通配符

使用 * 来匹配路径中存在的文件或者文件夹。说起来抽象,举个例子就明白了。假定当前目录下有这样几个文件:

file1.txt
file2.txt
file3.txt
dir1
dir1/file.txt
dir2
dir2/file.txt
dir3
dir3/file.txt

那么下面这个命令:

myprogram *

等价于:

myprogram file1.txt file2.txt file3.txt dir1 dir2 dir3

下面这个命令:

myprogram dir*

等价于:

myprogram dir1 dir2 dir3

需要注意的是,* 是由 bash 负责解释和展开的,而不是由 myprogram 负责解释和展开的; 或者说,myprogram 从来不知道 * 的存在,它收到的信息已经是展开后的结果了。

另一个常见的通配符是 ~,它表示当前用户的家目录(也就是你刚登陆时,所在的那个目录[^10])。例如:

myprogram ~/file.txt

可能等价于

myprogram /home/chn/file.txt

这个通配符也是由 bash 负责解释和展开的。 (等等,这个东西是通配符吗?)

for 循环,大括号展开

bash 也支持 for 循环,例如:

for i in 1 2 3; do mkdir $i; cd $i; cd ..; done

这个命令会在当前目录下创建三个文件夹,分别叫 123,然后依次进入这三个文件夹,再依次返回到当前目录。 这里用到了 ; 来分隔多个命令,它的具体用法在新手篇里不会提到,仅在这一个特殊的例子中照葫芦画瓢写就可以了。

对于数值,bash 还支持大括号展开,例如:




[^1]: 这个更底层的程序就是所谓的“内核”。
[^2]: 无论是 Windows 上的资源管理器还是 Linux 上的这个“黑框框”,它有一个统一的名字,叫做“shell”,也就是“壳”,意思就是它把系统中复杂的细节包装了起来,让用户可以简单地进行删除文件、打开文件夹等操作。
[^3]: Linux 上最常见的是 bash,学校的超算用的就是这个;次常见的是 zsh,我管理的其它服务器使用的其实就是 zsh 而不是 bash。
  这两个的基本使用方法是相同的,初学者不需要区分。
[^4]: WinSCP 默认不会列出隐藏的文件和文件夹,有时会误导新手以为 `.bashrc` 等文件不存在。
[^5]: 命令原本输出到屏幕上的内容不仅仅来自标准输出,还有标准错误输出(用于输出出错的信息,例如“permission denied”等)等途径。
  如此使用重定向符号只会重定向标准输出,不会重定向标准错误输出等其它输出。
[^6]: 实际上是“concatenate”的缩写,但这个单词我,反正我不认识。
[^7]: 系统在实际复制一个文件夹时,总是需要首先新建文件夹本身,然后新建文件夹里的
删除文件夹里的内容,而这又需要首先删除文件夹里的文件夹里的内容,以此类推。
  “recursive”就是“递归”的意思。单纯删除空文件夹的参数其实是 `-d`,意为“directory”,但这个参数很少用到,因为 `-r` 也可以删除空文件夹。
[^8]: 准确说,是 `PATH` 指向的目录。`rm` 等最常用的命令通常位于 `/usr/bin``/run/current-system/sw/bin`[^9]: 标准输入也不是程序读取用户输入的唯一途径。最常见的例外是一些需要读取密码的程序,为了防止密码泄漏,它们往往会从更底层直接拦截、读取掉。
  这也是为什么命令行中输入密码时,大多情况下没有任何显示的原因。
[^10]: 准确说,是 `HOME` 环境变量的值。
最后更新于