Linux入门教程

以下内容引用自jyy的操作系统实验课程网站, 并有少量修改和补充. 如果你是第一次使用Linux, 请你一边仔细阅读教程, 一边尝试运行教程中提到的命令.

探索命令行

Linux命令行中的命令使用格式都是相同的:

命令名称 参数1 参数2 参数3 ...

参数之间用任意数量的空白字符分开. 关于命令行, 可以先阅读一些基本常识. 然后我们介绍最常用的一些命令:

  • ls用于列出当前目录(即"文件夹")下的所有文件(或目录). 目录会用蓝色显示. ls -l可以显示详细信息.
  • pwd能够列出当前所在的目录.
  • cd DIR可以切换到DIR目录. 在Linux中, 每个目录中都至少包含两个目录: .指向该目录自身, ..指向它的上级目录. 文件系统的根是/.
  • touch NEWFILE可以创建一个内容为空的新文件NEWFILE, 若NEWFILE已存在, 其内容不会丢失.
  • cp SOURCE DEST可以将SOURCE文件复制为DEST文件; 如果DEST是一个目录, 则将SOURCE文件复制到该目录下.
  • mv SOURCE DEST可以将SOURCE文件重命名为DEST文件; 如果DEST是一个目录, 则将SOURCE文件移动到该目录下.
  • mkdir DIR能够创建一个DIR目录.
  • rm FILE能够删除FILE文件; 如果使用-r选项则可以递归删除一个目录. 删除后的文件无法恢复, 使用时请谨慎!
  • man可以查看命令的帮助. 例如man ls可以查看ls命令的使用方法. 灵活应用man和互联网搜索, 可以快速学习新的命令.

man的功能不仅限于此. man后可以跟两个参数, 可以查看不同类型的帮助(请在互联网上搜索). 例如当你不知道C标准库函数freopen如何使用时, 可以键入命令

man 3 freopen
学会使用man

如果你是第一次使用man, 请阅读这里. 这个教程除了说明如何使用man之外, 还会教你在使用一款新的命令行工具时如何获得帮助.

消失的cd

上述各个命令除了cd之外都能找到它们的manpage, 这是为什么? 如果你思考后仍然感到困惑, 试着到互联网上寻找答案.

下面给出一些常用命令使用的例子, 你可以键入每条命令之后使用ls查看命令执行的结果:

$ mkdir temp      # 创建一个目录temp
$ cd temp         # 切换到目录temp
$ touch newfile   # 创建一个空文件newfile
$ mkdir newdir    # 创建一个目录newdir
$ cd newdir       # 切换到目录newdir
$ cp ../newfile . # 将上级目录中的文件newfile复制到当前目录下
$ cp newfile aaa  # 将文件newfile复制为新文件aaa
$ mv aaa bbb      # 将文件aaa重命名为bbb
$ mv bbb ..       # 将文件bbb移动到上级目录
$ cd ..           # 切换到上级目录
$ rm bbb          # 删除文件bbb
$ cd ..           # 切换到上级目录
$ rm -r temp      # 递归删除目录temp
更多的命令行知识

仅仅了解这些最基础的命令行知识是不够的. 通常, 我们可以抱着如下的信条: 只要我们能想到的, 就一定有方便的办法能够办到. 因此当你想要完成某件事却又不知道应该做什么的时候, 请向Google求助. 如果你想以Linux作为未来的事业, 那就可以去图书馆或互联网上找一些相关的书籍来阅读.

统计代码行数

第一个例子是统计一个目录中(包含子目录)中的代码行数. 如果想知道当前目录下究竟有多少行的代码, 就可以在命令行中键入如下命令:

find . | grep '\.c$\|\.h$' | xargs wc -l

如果用man find查看find操作的功能, 可以看到find是搜索目录中的文件. Linux中一个点.始终表示Shell当前所在的目录, 因此find .实际能够列出当前目录下的所有文件. 如果在文件很多的地方键入find ., 将会看到过多的文件, 此时可以按CTRL + c退出.

同样, 用man查看grep的功能——"print lines matching a pattern". grep实现了输入的过滤, 我们的grep有一个参数, 它能够匹配以.c.h结束的文件. 正则表达式是处理字符串非常强大的工具之一, 每一个程序员都应该掌握其相关的知识. 有兴趣的同学可以首先阅读一个基础的教程, 然后看一个有趣的小例子: 如何用正则表达式判定素数. 正则表达式还可以用来编写一个30行的java表达式求值程序(传统方法几乎不可能), 聪明的你能想到是怎么完成的吗? 上述的grep命令能够提取所有.c.h结尾的文件.

刚才的findgrep命令, 都从标准输入中读取数据, 并输出到标准输出. 关于什么是标准输入输出, 请参考这里. 连接起这两个命令的关键就是管道符号|. 这一符号的左右都是Shell命令, A | B的含义是创建两个进程AB, 并将A进程的标准输出连接到B进程的标准输入. 这样, 将findgrep连接起来就能够筛选出当前目录(.)下所有以.c.h结尾的文件.

我们最后的任务是统计这些文件所占用的总行数, 此时可以用man查看wc命令. wc命令的-l选项能够计算代码的行数. xargs命令十分特殊, 它能够将标准输入转换为参数, 传送给第一个参数所指定的程序. 所以, 代码中的xargs wc -l就等价于执行wc -l aaa.c bbb.c include/ccc.h ..., 最终完成代码行数统计.

统计磁盘使用情况

以下命令统计/usr/share目录下各个目录所占用的磁盘空间:

du -sc /usr/share/* | sort -nr

du是磁盘空间分析工具, du -sc将目录的大小顺次输出到标准输出, 继而通过管道传送给sort. sort是数据排序工具, 其中的选项-n表示按照数值进行排序, 而-r则表示从大到小输出. sort可以将这些参数连写在一起.

然而我们发现, /usr/share中的目录过多, 无法在一个屏幕内显示. 此时, 我们可以再使用一个命令: moreless.

du -sc /usr/share/* | sort -nr | more

此时将会看到输出的前几行结果. more工具使用空格翻页, 并可以用q键在中途退出. less工具则更为强大, 不仅可以向下翻页, 还可以向上翻页, 同样使用q键退出. 这里还有一个关于less的小故事.

在Linux下编写Hello World程序

Linux中用户的主目录是/home/用户名称, 如果你的用户名是user, 你的主目录就是/home/user. 用户的home目录可以用波浪符号~替代, 例如临时文件目录/home/user/Templates可以简写为~/Templates. 现在我们就可以进入主目录并编辑文件了. 如果Templates目录不存在, 可以通过mkdir命令创建它:

cd ~
mkdir Templates

创建成功后, 键入

cd Templates

可以完成目录的切换. 注意在输入目录名时, tab键可以提供联想.

你感到键入困难吗?

你可能会经常要在终端里输入类似于

cd AVeryVeryLongFileName

的命令, 你一定觉得非常烦躁. 回顾上面所说的原则之一: 如果你感到有什么地方不对, 就一定有什么好办法来解决. 试试tab键吧.

Shell中有很多这样的小技巧, 你也可以使用其他的Shell例如zsh, 提供更丰富好用的功能. 总之, 尝试和改变是最重要的.

进入正确的目录后就可以编辑文件了, 开源世界中主流的两大编辑器是vi(m)emacs, 你可以使用其中的任何一种. 如果你打算使用emacs, 你还需要安装它

apt-get install emacs

viemacs这两款编辑器都需要一定的时间才能上手, 它们共同的特点是需要花较多的时间才能适应基本操作方式(命令或快捷键), 但一旦熟练运用, 编辑效率就比传统的编辑器快很多.

进入了正确的目录后, 输入相应的命令就能够开始编辑文件. 例如输入

vi hello.c
或emacs hello.c

就能开启一个文件编辑. 例如可以键入如下代码(对于首次使用viemacs的同学, 键入代码可能会花去一些时间, 在编辑的同时要大量查看网络上的资料):

#include <stdio.h>
int main(void) {
  printf("Hello, Linux World!\n");
  return 0;
}

保存后就能够看到hello.c的内容了. 终端中可以用cat hello.c查看代码的内容. 如果要将它编译, 可以使用gcc命令:

gcc hello.c -o hello

gcc-o选项指定了输出文件的名称, 如果将-o hello改为-o hi, 将会生成名为hi的可执行文件. 如果不使用-o选项, 则会默认生成名为a.out的文件, 它的含义是assembler output. 在命令行输入

./hello

就能够运行该程序. 命令中的./是不能少的, 点代表了当前目录, 而./hello则表示当前目录下的hello文件. 与Windows不同, Linux系统默认情况下并不查找当前目录, 这是因为Linux下有大量的标准工具(如test等), 很容易与用户自己编写的程序重名, 不搜索当前目录消除了命令访问的歧义.

使用重定向

有时我们希望将程序的输出信息保存到文件中, 方便以后查看. 例如你编译了一个程序myprog, 你可以使用以下命令对myprog进行反汇编, 并将反汇编的结果保存到output文件中:

objdump -d myprog > output

>是标准输出重定向符号, 可以将前一命令的输出重定向到文件output中. 这样, 你就可以使用文本编辑工具查看output了.

但你会发现, 使用了输出重定向之后, 屏幕上就不会显示myprog输出的任何信息. 如果你希望输出到文件的同时也输出到屏幕上, 你可以使用tee命令:

objdump -d myprog | tee output

使用输出重定向还能很方便地实现一些常用的功能, 例如

> empty                  # 创建一个名为empty的空文件
cat old_file > new_file  # 将文件old_file复制一份, 新文件名为new_file

如果myprog需要从键盘上读入大量数据(例如一个图的拓扑结构), 当你需要反复对myprog进行测试的时候, 你需要多次键入大量相同的数据. 为了避免这种无意义的重复键入, 你可以使用以下命令:

./myprog < data

<是标准输入重定向符号, 可以将前一命令的输入重定向到文件data中. 这样, 你只需要将myprog读入的数据一次性输入到文件data中, myprog就会从文件data中读入数据, 节省了大量的时间.

下面给出了一个综合使用重定向的例子:

time ./myprog < data | tee output

这个命令在运行myprog的同时, 指定其从文件data中读入数据, 并将其输出信息打印到屏幕和文件output中. time工具记录了这一过程所消耗的时间, 最后你会在屏幕上看到myprog运行所需要的时间. 如果你只关心myprog的运行时间, 你可以使用以下命令将myprog的输出过滤掉:

time ./myprog < data > /dev/null

/dev/null是一个特殊的文件, 任何试图输出到它的信息都会被丢弃, 你能想到这是怎么实现的吗? 总之, 上面的命令将myprog的输出过滤掉, 保留了time的计时结果, 方便又整洁.

使用Makefile管理工程

大规模的工程中通常含有几十甚至成百上千个源文件(Linux内核源码有25000+的源文件), 分别键入命令对它们进行编译是十分低效的. Linux提供了一个高效管理工程文件的工具: GNU Make. 我们首先从一个简单的例子开始, 考虑上文提到的Hello World的例子, 在hello.c所在目录下新建一个文件Makefile, 输入以下内容并保存:

hello:hello.c
    gcc hello.c -o hello    # 注意开头的tab, 而不是空格

.PHONY: clean

clean:
    rm hello    # 注意开头的tab, 而不是空格

返回命令行, 键入make, 你会发现make程序调用了gcc进行编译. Makefile文件由若干规则组成, 规则的格式一般如下:

目标文件名:依赖文件列表
    用于生成目标文件的命令序列   # 注意开头的tab, 而不是空格

我们来解释一下上文中的hello规则. 这条规则告诉make程序, 需要生成的目标文件是hello, 它依赖于文件hello.c, 通过执行命令gcc hello.c -o hello来生成hello文件.

如果你连续多次执行make, 你会得到"文件已经是最新版本"的提示信息, 这是make程序智能管理的功能. 如果目标文件已经存在, 并且它比所有依赖文件都要"新", 用于生成目标的命令就不会被执行. 你能想到make程序是如何进行"新"和"旧"的判断的吗?

上面例子中的clean规则比较特殊, 它并不是用来生成一个名为clean的文件, 而是用于清除编译结果, 并且它不依赖于其它任何文件. make程序总是希望通过执行命令来生成目标, 但我们给出的命令rm hello并不是用来生成clean文件, 因此这样的命令总是会被执行. 你需要键入make clean命令来告诉make程序执行clean规则, 这是因为make默认执行在Makefile中文本序排在最前面的规则. 但如果很不幸地, 目录下已经存在了一个名为clean的文件, 执行make clean会得到"文件已经是最新版本"的提示. 解决这个问题的方法是在Makefile中加入一行PHONY: clean, 用于指示"clean是一个伪目标". 这样以后, make程序就不会判断目标文件的新旧, 伪目标相应的命令序列总是会被执行.

对于一个规模稍大一点的工程, Makefile文件还会使用变量, 函数, 调用Shell命令, 隐含规则等功能. 如果你希望学习如何更好地编写一个Makefile, 请到互联网上搜索相关资料.

综合示例: 教务刷分脚本

使用编辑器编辑文件jw.sh为如下内容(另外由于教务网站的升级改版, 目前此脚本可能不能实现正确的功能):

#!/bin/bash
save_file="score" # 临时文件
semester=20102 # 刷分的学期, 20102代表2010年第二学期
jw_home="http://jwas3.nju.edu.cn:8080/jiaowu" # 教务网站首页地址
jw_login="http://jwas3.nju.edu.cn:8080/jiaowu/login.do" # 登录页面地址
jw_query="http://jwas3.nju.edu.cn:8080/jiaowu/student/studentinfo/achievementinfo.do?method=searchTermList&termCode=$semester" # 分数查询页面地址

name="09xxxxxxx" # 你的学号
passwd="xxxxxxxx" # 你的密码

# 请求jw_home地址, 并从中找到返回的cookie. cookie信息在http头中的JSESSIONID字段中
cookie=`wget -q -O - $jw_home --save-headers | \
    sed -n 's/Set-Cookie: JSESSIONID=\([0-9A-Z]\+\);.*$/\1/p'`
# 用户登录, 使用POST方法请求jw_login地址, 并在POST请求中加入userName和password
wget -q -O - --header="Cookie:JSESSIONID=$cookie" --post-data \
    "userName=${name}&password=${passwd}" "$jw_login" &> /dev/null
# 登录完毕后, 请求分数查询页面. 此时会返回html页面并输出到标准输出. 我们将输出重定向到文件"tmp"中.
wget -q -O - --header="Cookie:JSESSIONID=$cookie" "$jw_query" > tmp
# 获取分数列表. 因为教务网站的代码实在是实现得不太规整, 我们又想保留shell的风味, 所以用了比较繁琐的sed和awk处理. list变量中会包含课程名称的列表.
list=`cat tmp | sed -n '/<table.*TABLE_BODY.*>/,/<\/table>/p' \
        | sed '/<--/,/-->/d' | grep td \
        | awk 'NR%11==3' | sed 's/^.*>\(.*\)<.*$/\1/g'`
# 对list中的每一门课程, 都得到它的分数
for item in $list; do
    score=`cat tmp | grep -A 20 $item | awk "NR==18" | sed -n '/^.*\..*$/p'`
    score=`echo $score`
    if [[ ${#score} != 0 ]]; then # 如果存在成绩
        grep $item $save_file &>/dev/null # 查找分数是否显示过
        if [[ $? != 0 ]]; then # 如果没有显示过
        # 考虑到尝试的同学可能没有安装notify-send工具, 这里改成echo  -- yzh
            # notify-send "新成绩:$item $score" # 弹出窗口显示新成绩
            echo "新成绩:$item $score" # 在终端里输出新成绩
            echo $item >> $save_file # 将课程标记为已显示
        fi
    fi
done

运行这个例子需要在命令行中输入bash jw.sh, 用bash解释器执行这一脚本. 如果希望定期运行这一脚本, 可以使用Linux的标准工具之一: cron. 将命令添加到crontab就能实现定期自动刷新.

为了理解这个例子, 首先需要一些HTTP协议的基础知识. HTTP请求实际就是来回传送的文本流——浏览器(或我们例子中的爬虫)生成一个文本格式的HTTP请求, 包括header和content, 以文本的形式通过网络传送给服务器. 服务器根据请求内容(header中包含请求的URL以及浏览器等其他信息), 生成页面并返回.

用户登录的实现, 就是通过HTTP头中header中的cookie实现的. 当浏览器第一次请求页面时, 服务器会返回一串字符, 用来标识浏览器的这次访问. 从此以后, 所有与该网站交互时, 浏览器都会在HTTP请求的header中加入这个字符串, 这样服务器就"记住"了浏览器的访问. 当完成登录操作(将用户名和密码发送到服务器)后, 服务器就知道这个cookie隐含了一个合法登录的帐号, 从而能够根据帐号信息发送成绩.

得到包含了成绩信息的html文档之后, 剩下的事情就是解析它了. 我们用了大量的sedawk完成这件事情, 同学们不用去深究其中的细节, 只需知道我们从文本中提取出了课程名和成绩, 并且将没有显示过的成绩显示出来.

我们讲解这个例子主要是为了说明新环境下的工作方式, 以及实践Unix哲学:

  • 每个程序只做一件事, 但做到极致
  • 用程序之间的相互协作来解决复杂问题
  • 每个程序都采用文本作为输入和输出, 这会使程序更易于使用

一个Linux老手可以用脚本完成各式各样的任务: 在日志中筛选想要的内容, 搭建一个临时HTTP服务器(核心是使用nc工具)等等. 功能齐全的标准工具使Linux成为工程师, 研究员和科学家的最佳搭档.

results matching ""

    No results matching ""