Linux 系统可划分为以下 4 部分。
内核主要负责以下 4 种功能。
$ systemctl get-default
graphical.target
GNU 实用工具
| shell | 描 述 |
|---|---|
| ash | 一种简单的轻量级 shell,运行在内存受限环境中, 但与 bash shell 完全兼容 |
| korn | 一种与 Bourne shell 兼容的编程 shell,但支持如关联数组和浮点算术等高级编程特性 |
| tcsh | 一种将 C语言中的一些元素引入 shell脚本中的 shell |
| zsh | 一种结合了 bash、tcsh 和 korn 的特性,同时提供高级编程特性、共享历史文件和主题化提示符的高级shell |
多数 Linux 发行版也有 LiveCD 版本可用。LiveCD 版本是一个自成一体(self-contained)的 ISO 镜像文件,可以刻录成 DVD(或写入 U 盘),直接引导 Linux 系统,无须安装在硬盘上。LiveDVD 的好处是可以在安装系统之前先测试系统硬件, 解决存在的问题。
GNU bash shell 是一个程序, 提供了对 Linux 系统的交互式访问。它是作为普通程序运行的,通常是在用户登录终端时启动。系统启动的 shell 程序取决于用户账户的配置。
/etc/passwd 文件包含了所有系统用户账户以及每个用户的基本配置信息。
man 命令可以访问 Linux 系统的手册页。在 man 命令之后跟上想要查看的命令名,就可以显示相应的手册页。
当你使用 man 命令查看命令手册页的时候,其中的信息是由分页程序(pager )来显示的。 分页程序是一种实用工具,能够逐页(或逐行)显示文本。你可以单击空格键进行翻页, 或是使用 Enter 键逐行查看。也可以使用箭头键向前和向后滚动手册页的内容(假设你使用的终端仿真软件包支持箭头键功能)。如果阅读完毕,可以按 q 键退出手册页。
输入man man 可以查看与手册页相关的信息。
如果想使用多个命令选项,那么通常可以将其合并在一起。例如,要使用选项-a 和 -b,可以写作-ab。
如果不记得命令名了,可以使用关键字来搜索手册页。语法为 man -k keyword。例如,要查找与终端相关的命令,可以输入 man -k terminal。
手册页中还有不同的节。每节都分配了一个数字,从 1 开始,一直到 9。Linux 手册页的节如下表:
| 节号 | 所涵盖的内容 |
|---|---|
| 1 | 可执行程序或 shell 命令 |
| 2 | 系统调用 |
| 3 | 库调用 |
| 4 | 特殊文件 |
| 5 | 文件格式与约定 |
| 6 | 游戏 |
| 7 | 概览、约定及杂项 |
| 8 | 超级用户和系统管理员命令 |
| 9 | 内核例程(routine) |
man 命令通常显示的是指定命令编号最低的节。(一个命令偶尔会在多个节中都有对应的手册页。)
大多数命令接受-h 或–help 选项。例如,可以输入 hostname --help 来查看简要的帮助信息。
Linux 文件系统
| 目录 | 用途 |
|---|---|
| / | 虚拟目录的根目录,通常不会在这里放置文件 |
| /bin | 二进制文件目录,存放了很多用户级的 GNU 实用工具 |
| /boot | 引导目录,存放引导文件 |
| /dev | 设备目录, Linux 在其中创建设备节点 |
| /etc | 系统配置文件目录 |
| /home | 主目录, Linux 在其中创建用户目录(可选) |
| /lib | 库目录, 存放系统和应用程序的库文件 |
| /libname | 库目录, 存放替代格式的系统和应用程序库文件(可选) |
| /media | 媒介目录,可移动存储设备的常用挂载点 |
| /mnt | 挂载目录,用于临时挂载文件系统的常用挂载点 |
| /opt | 可选目录,存放第三方软件包 |
| /proc | 进程目录,存放现有内核、系统以及进程的相关信息 |
| /root | root 用户的主目录(可选) |
| /run | 运行目录,存放系统的运行时数据 |
| /sbin | 系统二进制文件目录,存放了很多管理级的 GNU 实用工具 |
| /srv | 服务目录,存放本地服务的相关文件 |
| /sys | 系统目录,存放设备、驱动程序以及部分内核特性信息 |
| /tmp | 临时目录,可以在其中创建和删除临时工作文件 |
| /usr | 用户目录,一个次目录层级结构(secondary directory hierarchy) |
| /var | 可变目录,存放经常变化的文件,比如日志文件 |
// 例如方括号:方括号代表单个字符位置并给出了该位置上的多个可能的选择。
$ ls -l my_scr[ay]pt
// 也可以指定字符范围,比如字母范围 [a–i]:
$ ls f[a-i]ll
// 还可以使用惊叹号(!)将不需要的内容排除在外:
$ ls -l f[!a]ll
$ cp -i test_one /home/christine/Documents/
cp -R Documents/ NewDocuments/
制表键补全允许你在输入文件名 或目录名的时候,按一下制表键, 让 shell 帮你将内容补充完整。
链接文件
$ ln -s test_file slink_test_file
$ ls -l *test_file
lrwxrwxrwx. 1 christine christine 9 Mar 4 09:46 slink_test_file -> test_file
-rw-rw-r--. 1 christine christine 74 Feb 29 15:50 test_file
$ ls -i *test_file
1415020 slink_test_file 1415523 test_file
$ ls -l *test_one
-rw-rw-r--. 1 christine christine 0 Feb 29 17:26 test_one
$ ln test_one hlink_test_one
$ ls -li *test_one
1415016 -rw-rw-r--. 2 christine christine 0 Feb 29 17:26 hlink_test_one
1415016 -rw-rw-r--. 2 christine christine 0 Feb 29 17:26 test_one
mv OldDocuments NewDocuments$ rm -i fall
rm: remove regular empty file 'fall'? y
$ mkdir New_Dir
$ ls -ld New_Dir
drwxrwxr-x. 2 christine christine 6 Mar 6 14:40 New_Dir
$ mkdir -p New_Dir/SubDir/UnderDir
$ ls -R New_Dir
New_Dir:
SubDir
New_Dir/SubDir:
UnderDir
New_Dir/SubDir/UnderDir:
$ file .bashrc
.bashrc: ASCII text
$ file Documents
Documents/: directory
$ file slink_test_file
slink_test_file: symbolic link to test_file
$ file my_script
my_script: Bourne-Again shell script, ASCII text executable
$ file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 3.2.0,[...]
$ tail -n 2 log_file
$ head -3 log_file
| 选项 | 描述 |
|---|---|
| -A | 显示所有进程 |
| -N | 显示与指定参数不符的所有进程 |
| -a | 显示除控制进程(session leader)和无终端进程外的所有进程 |
| -d | 显示除控制进程外的所有进程 |
| -e | 显示所有进程 |
| -C cmdlist | 显示包含在 cmdlist 列表中的进程 |
| -G grplist | 显示组 ID 在 grplist 列表中的进程 |
| -U userlist | 显示属主的用户 ID 在 userlist 列表中的进程 |
| -g grplist | 显示会话或组 ID 在 grplist 列表中的进程 |
| -p pidlist | 显示 PID 在 pidlist 列表中的进程 |
| -s sesslist | 显示会话 ID 在 sesslist 列表中的进程 |
| -t ttylist | 显示终端 ID 在 ttylist 列表中的进程 |
| -u userlist | 显示有效用户 ID 在 userlist 列表中的进程 |
| -F | 显示更多的额外输出(相对-f 选项而言) |
| -O | 显示默认的输出列以及 format 列表指定的特定列 |
| -M | 显示进程的安全信息 |
| -c | 显示进程的额外的调度器信息 |
| -f | 显示完整格式的输出 |
| -j | 显示作业信息 |
| -l | 显示长列表 |
| -o format | 仅显示由 format 指定的列 |
| -y | 不显示进程标志 |
| -Z | 显示安全上下文信息 |
| -H | 以层级格式显示进程(显示父进程) |
| -n namelist | 定义要在 WCHAN 输出列中显示的值 |
| -w | 采用宽输出格式,不限宽度显示 |
| -L | 显示进程中的线程 |
| -V | 显示 ps 命令的版本号 |
| 选项 | 描述 |
|---|---|
| T | 显示与当前终端关联的所有进程 |
| a | 显示与任意终端关联的所有进程 |
| g | 显示包括控制进程在内的所有进程 |
| r | 仅显示运行中的进程 |
| x | 显示所有进程, 包括未分配任何终端的进程 |
| U userlist | 显示属于 userlist 列表中某个用户 ID 所有的进程 |
| p pidlist | 显示 PID 在 pidlist 列表中的进程 |
| t ttylist | 显示与 ttylist 列表中的某个终端关联的进程 |
| O format | 除了标准列,还输出由 format 指定的列 |
| X | 以寄存器格式显示数据 |
| Z | 在输出中包含安全信息 |
| j | 显示作业信息 |
| l | 采用长格式显示 |
| o format | 仅显示由 format 指定的列 |
| s | 采用信号格式显示 |
| u | 采用基于用户的格式显示 |
| v | 采用虚拟内存格式显示 |
| N namelist | 定义要在 WCHAN 输出列中显示的值 |
| O order | 定义信息列的显示顺序 |
| S | 将子进程的数值统计信息(比如 CPU 和内存使用情况)汇总到父进程中 |
| c | 显示真实的命令名称(用以启动该进程的程序名称) |
| e | 显示命令使用的环境变量 |
| f | 用层级格式来显示进程, 显示哪些进程启动了哪些进程 |
| h | 不显示头信息 |
| k soft | 指定用于排序输出的列 |
| n | 使用数值显示用户 ID 、组 ID 以及 WCHAN 信息 |
| w | 为更宽的终端屏幕生成宽输出 |
| H | 将线程显示为进程 |
| m | 在进程之后显示线程 |
| L | 列出所有的格式说明符 |
| V | 显示 ps 命令的版本 |
| 选项 | 描述 |
|---|---|
| –deselect | 显示除命令行中列出的进程之外的其他进程 |
| –Group grplist | 显示组 ID 在 grplist 列表中的进程 |
| –User userlist | 显示用户 ID 在 userlist 列表中的进程 |
| –group grplist | 显示有效组 ID 在 grplist 列表中的进程 |
| –user userlist | 显示有效用户 ID 在 userlist 列表中的进程 |
| –pid pidlist | 显示 pid 在 pidlist 列表中的进程 |
| –ppid pidlist | 显示父 pid 在 pidlist 列表中的进程 |
| –sid sidlist | 显示会话 ID 在 sidlist 列表中的进程 |
| –tty ttylist | 显示终端设备 ID 在 ttylist 列表中的进程 |
| –format format | 仅显示由 format 指定的列 |
| –context | 显示额外的安全信息 |
| –cols n | 将屏幕宽度设置为 n 列 |
| –columns n | 将屏幕宽度设置为 n 列 |
| –cumulative | 包含已停止的子进程的信息 |
| –forest | 用层级结构显示出进程和父进程之间的关系 |
| –headers | 在每页输出中都显示列名 |
| –no-headers | 不显示列名 |
| –lines n | 将屏幕高度设置为 n 行 |
| –rows n | 将屏幕高度设置为 n 行 |
| –sort order | 指定用于排序输出的列 |
| –width n | 将屏幕宽度设置为 n 列 |
| –help | 显示帮助信息 |
| –info | 显示调试信息 |
| –version | 显示 ps 命令的版本号 |
在 Linux 中,进程之间通过信号来通信。进程的信号是预定义好的一个消息,进程能识别该消息并决定忽略还是做出反应。进程如何处理信号是由开发人员通过编程来决定的。大多数编写完善的应用程序能接收和处理标准 Unix 进程信号。
Linux 进程信号
| 信号 | 名称 | 描述 |
|---|---|---|
| 1 | HUP | 挂起 |
| 2 | INT | 中断 |
| 3 | QUIT | 结束运行 |
| 9 | KILL | 无条件终止 |
| 11 | SEGV | 段错误 |
| 15 | TERM | 尽可能终止 |
| 17 | STOP | 无条件停止运行,但不终止 |
| 18 | TSTP | 停止或暂停,但继续在后台运行 |
| 19 | CONT | 在 STOP 或 TSTP 之后恢复执行 |
在 Linux 中有两个命令可以向运行中的进程发出进程信号: kill 和 pkill。
Linux 文件系统会将所有的磁盘都并入单个虚拟目录。在使用新的存储设备之前,需要将其放在虚拟目录中。这项工作称为挂载(mounting)。
用于挂载存储设备的命令叫作 mount。在默认情况下, mount 命令会输出当前系统已挂载的设备列表。但是, 除了标准存储设备, 较新版本的内核还会挂载大量用作管理目的的虚拟文件系统。
如果知道设备分区使用的文件系统类型,可以像下面这样过滤输出。
$ mount -t ext4
mount 命令提供了 4 部分信息。
要手动在虚拟目录中挂载设备,需要以 root 用户身份登录,或是以 root 用户身份运行 sudo 命令。下面是手动挂载设备的基本命令:
mount -t type device directory
mount -t vfat /dev/sdb1 /media/disk
一旦存储设备被挂载到虚拟目录, root 用户就拥有了对该设备的所有访问权限,而其他用户的访问则会被限制。可以通过目录权限指定用户对设备的访问权限。
mount 命令选项
| 选项 | 描述 |
|---|---|
| -a | 挂载/etc/fstab 文件中指定的所有文件系统 |
| -f | 模拟挂载设备, 但并不真正挂载 |
| -F | 和-a 选项一起使用时,同时挂载所有文件系统 |
| -v | 详细模式,显示挂载设备的每一步操作 |
| -i | 不使用/sbin/mount.filesystem 下的任何文件系统协助文件 |
| -l | 自动给 ext2 、ext3 、ext4 或 XFS 文件系统添加文件系统标签 |
| -n | 挂载设备,但不在/etc/mtab 已挂载设备文件中注册 |
| -p num | 进行加密挂载时从文件描述符 num 中获得口令 |
| -s | 忽略该文件系统不支持的挂载选项 |
| -r | 将设备挂载为只读 |
| -w | 将设备挂载为可读写(默认选项) |
| -L label | 将设备按指定的 label 挂载 |
| -U uuid | 将设备按指定的 uuid 挂载 |
| -O | 和-a 选项一起使用,限制其所作用的文件系统 |
| -o | 给文件系统添加特定的选项 |
mount 命令的 -o 选项允许在挂载文件系统时添加一系列以逗号分隔的额外选项。常用选项如下。
移除可移动设备时,不能直接将设备拔下,应该先卸载。卸载设备的命令是 umount。
Linux 不允许直接弹出已挂载的 CD 或 DVD 。如果在从光驱中移除 CD 或 DVD 时遇到麻烦,那么最大的可能是它还在虚拟目录中挂载着。应该先卸载, 然后再尝试弹出。
umount 命令的格式:
umount [directory | device ]
umount 命令支持通过设备文件或者挂载点来指定要卸载的设备。如果有任何程序正在使用设备上的文件,则系统将不允许卸载该设备。
如果在卸载设备时,系统提示设备繁忙,无法卸载,那么通常是有进程还在访问该设备或使用该设备上的文件。这时可用 lsof 命令获得相关进程的信息,然后将进程终止。 lsof 命令的用法很简单: lsof /path/to/device/node, 或者 lsof /path/to/mount/point。
处理大量数据时的一个常用命令是 sort。sort 可以轻松地对大数据文件进行排序。
在默认情况下,sort 命令会依据会话所指定的默认语言的排序规则来对文本文件中的数据行进行排序。
在默认情况下,sort 命令会将数字视为字符并执行标准的字符排序,这种结果可能不是你想要的。可以使用-n 选项来解决这个问题,该选项会告诉 sort 命令将数字按值排序。
另一个常用的选项是-M,该选择可以将数字按月排序,sort 命令就能识别三字符的月份名(例如,Jan、Feb等)并正确排序。 Linux 的日志文件经常在每行的起始位置有一个时间戳,以表明事件是什么时候发生的。
sort 命令选项
| 短选项 | 长选项 | 描述 |
|---|---|---|
| -b | –ignore-leading-blanks | 排序时忽略起始的空白字符 |
| -C | –check=quiet | 不排序, 如果数据无序也不要报告 |
| -c | –check | 不排序, 但检查输入数据是否有序,无序的话就报告 |
| -d | –dictionary-order | 仅考虑空白字符和字母数字字符,不考虑特殊字符 |
| -f | –ignore-case | 大写字母默认先出现,该选项会忽略大小写 |
| -g | –general-numeric-sort | 使用一般数值进行排序 |
| -i | –ignore-nonprinting | 在排序时忽略不可打印字符 |
| -k | –key=POS1 [,POS2] | 排序键从 POS1 位置开始, 到 POS2 位置结束(如果指定了 POS2 的话) |
| -M | –month-sort | 用三字符的月份名按月份排序 |
| -m | –merge | 合并两个已排序数据文件 |
| -n | –numeric-sort | 将字符串按数值意义排序 |
| -o | –output=file | 将排序结果写入指定文件 |
| -R | –random-sort | 根据随机哈希排序 |
| -R | –random-source=FILE | 指定-R 选项用到的随机字节文件 |
| -r | –reverse | 逆序排序(升序变成降序) |
| -S | –buffer-size=SIZE | 指定使用的内存大小 |
| -s | –stable | 禁止 last-resort 比较,实现稳定排序 |
| -T | –temporary-directory=DIR | 指定用于保存临时工作文件的目录 |
| -t | –field-separator=SEP | 指定字段分隔符 |
| -u | –unique | 和-c 选项合用时,检查严格排序;不和-c 选项合用时,相同行仅输出一次 |
| -z | –zero-terminated | 在行尾使用 NULL 字符代替换行符 |
$ sort -t ':' -k 3 -n /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
...
$ du -sh * | sort -nr
1008k mrtg-2.9.29.tar.gz
972k bldg1
888k fbs2.pdf
760k Printtest
...
grep [options] pattern [file]
$ grep [tf] file1
Linux 文件压缩工具
| 工具 | 文件扩展名 | 描述 |
|---|---|---|
| bzip2 | .bz2 | 采用 Burrows-Wheeler 块排序文本压缩算法和霍夫曼编码 |
| compress | .Z | 最初的 Unix 文件压缩工具,已经快要无人使用了 |
| gzip | .gz | GNU 压缩工具,用 Lempel-Zivwelch 编码 |
| xz | .xz | 日渐流行的通用压缩工具 |
| zip | .zip | Windows 中 PKZIP 工具的 Unix 实现 |
gzip 软件包包括以下文件。
gzip 命令会压缩命令行中指定的文件。也可以指定多个文件名或是用通配符来一次性压缩多个文件,gzip 命令会压缩该目录中匹配通配符的每个文件。
tar 命令的格式如下。
tar function [options] object1 object2 ...
tar 命令的操作
| 操作 | 长选项 | 描述 |
|---|---|---|
| -A | –concatenate | 将一个 tar 归档文件追加到另一个 tar 归档文件末尾 |
| -c | –create | 创建新的 tar 归档文件 |
| -d | –diff | 检查归档文件和文件系统的不同之处 |
| –delete | 从 tar 归档文件中删除文件 | |
| -r | –append | 将文件追加到 tar 归档文件末尾 |
| -t | –list | 列出 tar 归档文件的内容 |
| -u | –update | 将比 tar 归档文件中已有的同名文件更新的文件追加到该归档文件 |
| -x | –extract | 从 tar 归档文件中提取文件 |
每种操作都使用 option(选项)来定义针对 tar 归档文件的具体行为。
tar 命令选项
| 选项 | 描述 |
|---|---|
| -C dir | 切换到指定目录 |
| -f file | 将结果输出到文件(或设备) |
| -j | 将输出传给 bzip2 命令进行压缩 |
| -J | 将输出传给 xz 命令进行压缩 |
| -p | 保留文件的所有权限 |
| -v | 在处理文件时显示文件名 |
| -z | 将输出传给 gzip 命令进行压缩 |
| -Z | 将输出传给 compress 命令进行压缩 |
// 该命令创建了一个名为 test.tar 的归档文件,包含目录 test和 test2 的内容
tar -cvf test.tar test/ test2/
// 该命令列出了(但不提取)tar 文件 test.tar 的内容
tar -tf test.tar
// 该命令从 tar 文件 test.tar 中提取内容。如果创建的时候 tar 文件含有目录结构,则在当前目 录中重建该目录的整个结构。
tar -xvf test.tar
在下载开源软件时经常会看到文件名以.tgz 结尾,这是经 gzip压缩过的 tar 文件,可以用命令 tar -zxvf filename.tgz 来提取其中的内容。
$ echo $0
-bash
$ dash
$ echo $0
dash
$ exit
$ echo $0
-bash
| 选项 | 描述 |
|---|---|
| -c string | 从 string 中读取命令并进行处理 |
| -i | 启动一个能够接收用户输入的交互式 shell |
| -l | 作为登录 shell 启动 |
| -r | 启动一个受限 shell,将用户限制在默认目录中 |
| -s | 从标准输入中读取命令 |
$ pwd ; ls test* ; cd /etc ; pwd ; cd ; pwd ; ls my*
$ (pwd ; ls test* ; cd /etc ; pwd ; cd ; pwd ; ls my*)
在交互式 shell 中, 一种高效的子 shell 用法是后台模式。
sleep 命令会接受一个参数作为希望进程等待(睡眠)的秒数。该命令在 shell 脚本中常用于引入一段暂停时间。命令 sleep 10 会将会话暂停 10 秒,然后返回 shell CLI 提示符。
$ sleep 3000&
[1] 2542
$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 2356 2352 0 13:27 pts/0 00:00:00 -bash
christi+ 2542 2356 0 13:44 pts/0 00:00:00 sleep 3000
christi+ 2543 2356 0 13:44 pts/0 00:00:00 ps -f
$ jobs
[1]+ Running sleep 3000 &
$ jobs -l
[1]+ 2542 Running sleep 3000 &
$
[1]+ Done sleep 3000
通过将进程列表置入后台,可以在子 shell 中进行大量的多进程处理。由此带来的一个好处是终端不再和子 shell 的 I/O 绑定在一起。
$ (tar -cf Doc.tar Documents ; tar -cf Music.tar Music)&
[1] 2567
$
$ ls *.tar
Doc.tar Music.tar
[1]+ Done ( tar -cf Doc.tar Documents; tar -cf Music.tar Music )
$
$ coproc sleep 10
[1] 2689
$ jobs
[1]+ Running coproc COPROC sleep 10 &
$ coproc My_Job { sleep 10; }
$ jobs
[1]+ Running coproc My_Job { sleep 10; } &
$ alias li='ls -i'
bash shell 使用环境变量来存储 shell 会话和工作环境的相关信息(这也是被称作环境变量的原因)。环境变量允许在内存中存储数据, 以便 shell 中运行的程序或脚本能够轻松访问到这些数据。这也是存储持久数据的一种简便方法。
bash shell 中有两种环境变量。
$ my_variable=Hello
$ echo $my_variable
Hello
$ my_variable="Hello World"
$ echo $my_variable
Hello World
$ my_variable="I am Global now"
$ export my_variable
或者:可以将设置变量和导出变量放在一个命令里完成。
$ export my_variable="I am Global Now"
bash shell 与 Unix Bourne shell 兼容的环境变量。bash shell 支持的 Bourne 变量
| 变量 | 描述 |
|---|---|
| CDPATH | 以冒号分隔的目录列表, 作为 cd 命令的搜索路径 |
| HOME | 当前用户的主目录 |
| IFS | shell 用来将文本字符串分割成字段的若干字符 |
| 当前用户收件箱的文件名(bash shell 会检查这个文件来确认有没有新邮件) | |
| MAILPATH | 以冒号分隔的当前用户收件箱的文件名列表(bash shell 会检查列表中的每个文件来确认有没有新邮件) |
| OPTARG | 由 getopt 命令处理的最后一个选项参数 |
| OPTIND | 由 getopt 命令处理的最后一个选项参数的索引 |
| PATH | shell 查找命令时使用的目录列表, 以冒号分隔 |
| PS1 | shell 命令行的主提示符 |
| PS2 | shell 命令行的次提示符 |
除了默认的 Bourne 环境变量, bash shell 还提供一些自有的变量。bash shell 环境变量
| 变量 | 描述 |
|---|---|
| BASH | bash shell 当前实例的完整路径名 |
| BASH_ALIASES | 关联数组,包含当前已设置的别名 |
| BASH_ARGC | 数组变量,包含传入函数或 shell 脚本的参数个数 |
| BASH_ARCV | 数组变量,包含传入函数或 shell 脚本的参数 |
| BASH_ARCV0 | 包含 shell 的名称或 shell 脚本的名称(如果在脚本中使用的话) |
| BASH_CMDS | 关联数组,包含 shell 已执行过的命令的位置 |
| BASH_COMMAND | 正在执行或将要执行的 shell 命令 |
| BASH_COMPAT | 指定 shell 兼容级别的值 |
| BASH_ENV | 如果设置的话, bash 脚本会在运行前先尝试运行该变量定义的启动文件 |
| BASH_EXECUTION_STRING | 使用 bash 命令的-c 选项传递过来的命令 |
| BASH_LINENO | 数组变量,包含当前正在执行的 shell 函数在源文件中的行号 |
| BASH_LOADABLE_PATH | 以冒号分隔的目录列表, shell 会在其中查找可动态装载的内建命令 |
| BASH_REMATCH | 只读数组变量, 在使用正则表达式的比较运算符=~进行肯定匹配(positive match)时, 包含整个模式及子模式所匹配到的内容 |
| BASH_SOURCE | 数组变量,包含当前正在执行的 shell 函数所在的源文件名 |
| BASH_SUBSHELL | 当前子 shell 环境的嵌套级别(初始值是 0 ) |
| BASH_VERSINFO | 数组变量,包含 bash shell 当前实例的主版本号和次版本号 |
| BASH_VERSION | bash shell 当前实例的版本号 |
| BASH_XTRACEFD | 如果设置为有效的文件描述符( 0、1、2),则 'set -x’调试选项生成的跟踪输出可被 重定向。通常用于将跟踪信息输出到文件中 |
| BASHOPTS | 当前启用的 bash shell选项 |
| BASHPID | 当前 bash进程的 PID |
| CHILD_MAX | 设置 shell 能够记住的已退出子进程状态的数量 |
| COLUMNS | bash shell 当前实例所用的终端显示宽度 |
| COMP_CWORD | 变量 COMP_WORDS 的索引,其中包含当前光标的位置 |
| COMP_LINE | 当前命令行 |
| COMP_POINT | 相对于当前命令起始处的光标位置索引 |
| COMP_KEY | 用来调用 shell 函数补全功能的最后一个按键 |
| COMP_TYPE | 一个整数值,指明了用以完成 shell 函数补全所尝试的补全类型 |
| COMP_WORDBREAKS | Readline 库中用于单词补全的分隔符 |
| COMP_WORDS | 数组变量,包含当前命令行所有单词 |
| COMPREPLY | 数组变量,包含由 shell 函数生成的可能的补全代码 |
| COPROC | 数组变量,包含用于匿名协程 I/O 的文件描述符 |
| DIRSTACK | 数组变量,包含目录栈的当前内容 |
| EMACS | 设置为’t’时,表明 emacs shell 缓冲区正在工作,行编辑功能被禁止 |
| EPOCHREALTIME | 包含自 Unix 纪元时( 1970 年 1 月 1 日 00:00:00 UTC )以来的秒数,包括微秒 |
| EPOCHSECONDS | 包含自 Unix 纪元时( 1970 年 1 月 1 日 00:00:00 UTC )以来的秒数,不包括微秒 |
| ENV | 如果设置,则会在 bash shell 脚本运行之前先执行已定义的启动文件(仅当 bash shell 以 POSIX 模式被调用时) |
| EUID | 当前用户的有效用户 ID (数字形式) |
| EXECIGNORE | 以冒号分隔的过滤器列表, 在使用 PATH 搜索命令时, 用于决定要忽略的可执行文件 (比如共享库文件) |
| FCEDIT | 供 fc 命令使用的默认编辑器 |
| FIGNORE | 在进行文件名补全时可以忽略后缀名列表,以冒号分隔 |
| FUNCNAME | 当前正在执行的 shell 函数的名称 |
| FUNCNEST | 当设置成非 0 值时, 表示所允许的函数最大嵌套级数(一旦超出, 当前命令即被终止) |
| GLOBIGNORE | 以冒号分隔的模式列表,定义了在进行文件名扩展时可以忽略的一组文件名 |
| GROUPS | 数组变量,包含当前用户的属组 |
| histchars | 控制历史记录扩展,最多可有 3 个字符 |
| HISTCMD | 当前命令在历史记录中的编号 |
| HISTCONTROL | 控制哪些命令留在历史记录列表中 |
| HISTFILE | 保存 shell 历史记录的文件名(默认是.bash_history) |
| HISTFILESIZE | 历史记录文件(history file)能保存的最大命令数量 |
| HISTIGNORE | 以冒号分隔的模式列表, 用于决定忽略历史文件中的哪些命令 |
| HISTSIZE | 能写入历史记录列表( history list)的最大命令数量① |
| HISTTIMEFORMAT | 如果设置且不为空,则作为格式化字符串,用于打印 bash 历史记录中命令的时间戳 |
| HOSTFILE | shell 在补全主机名时读取的文件名 |
| HOSTNAME | 当前主机的名称 |
| HOSTTYPE | 字符串, 用于描述当前运行 bash shell 的机器 |
| IGNOREEOF | shell 在退出前必须连续接收到的 EOF字符数量(如果该值不存在, 则默认为 1 ) |
| INPUTRC | Readline 的初始化文件名(默认为.inputrc) |
| INSIDE_EMACS | 仅当进程在 Emacs 编辑器的缓冲区中运行时才设置, 并且可以禁用行编辑(行编辑的 禁用也取决于 TERM 变量的值) |
| LANG | shell 的语言环境种类(locale category) |
| LC_ALL | 定义语言环境种类,能够覆盖 LANG 变量 |
| LC_COLLATE | 设置字符串排序时采用排序规则 |
| LC_CTYPE | 决定如何解释出现在文件名扩展和模式匹配中的字符 |
| LC_MESSAGES | 决定在解释前面带有$的双引号字符串时采用的语言环境设置 |
| LC_NUMERIC | 决定格式化数字时采用的语言环境设置 |
| LC_TIME | 决定格式化日期和时间时采用的语言环境设置 |
| LINENO | 当前正在执行的脚本语句的行号 |
| LINES | 定义了终端上可见的行数 |
| MACHTYPE | 用“CPU–公司–系统”(CPU-company-system)格式定义的系统类型 |
| MAILCHECK | shell 应该多久检查一次新邮件(以秒为单位,默认为 60 秒) |
| MAPFILE | 数组变量,当未指定数组变量作为参数时,其中保存了 mapfile 所读入的文本 |
| OLDPWD | shell 先前使用的工作目录 |
| OPTERR | 如果设置为 1 ,则 bash shell会显示 getopts 命令产生的错误 |
| OSTYPE | 定义了 shell 所在的操作系统 |
| PIPESTATUS | 数组变量,包含前台进程的退出状态 |
| POSIXLY_CORRECT | 如果设置的话, bash 会以 POSIX 模式启动 |
| PPID | bash shell父进程的 PID |
| PROMPT_COMMAND | 如果设置的话,在显示命令行主提示符之前执行该命令 |
| PROMPT_DIRTRIM | 用来定义使用提示符字符串\w 和\W转义时显示的拖尾(trailing)目录名的数量(使用 一组英文句点替换被删除的目录名) |
| PS0 | 如果设置的话,指定了在输入命令之后、执行命令之前由交互式 shell 显示的内容 |
| PS3 | select 命令的提示符 |
| PS4 | 在命令行之前显示的提示符(如果使用了 bash的-x 选项的话) |
| PWD | 当前工作目录 |
| RANDOM | 返回一个 0 ~ 32 767 的随机数(对该变量的赋值可作为随机数生成器的种子) |
| READLINE_LINE | 当使用 bind –x 命令时, 保存 Readline 缓冲区的内容 |
| READLINE_POINT | 当使用 bind –x 命令时, 指明了Readline 缓冲区内容插入点的当前位置 |
| REPLY | read 命令的默认变量 |
| SECONDS | 自 shell 启动到现在的秒数(对其赋值会重置计数器) |
| SHELL | bash shell 的完整路径名 |
| SHELLOPTS | 以冒号分隔的已启用的 bash shell选项 |
| SHLVL | shell 的层级,每启动一个新的 bash shell,该值增加 1 |
| TIMEFORMAT | 指定了 shell 的时间显示格式 |
| TMOUT | select 命令和 read 命令在无输入的情况下等待多久(以秒为单位,默认值为 0 ,表 示一直等待) |
| TMPDIR | 目录名, 保存 bash shell 创建的临时文件 |
| UID | 当前用户的真实用户 ID (数字形式) |
HISTFILESIZE 和 HISTSIZE 这两个环境变量的区别。先要区分“历史记录列表”和“历史记 录文件”。前者位于内存中,在 bash 会话进行期间更新。后者位于硬盘上, 在 bash shell 中通常是~/.bash_history。 会话结束后,历史记录列表中的内容会被写入历史记录文件。如果 HISTFILESIZE=200,表示历史记录文件中最多能保存 200 个命令;如果 HISTSIZE=20,表示不管输入多少命令,历史记录列表中只记录 20 个命令,最终也只有这 20个命令会在会话结束后被写入历史记录文件。
不是所有的默认环境变量都会在 set 命令的输出中列出。如果用不到,默认环境变量并不要求必须有值。
系统使用的默认环境变量有时取决于 bash shell 的版本。例如, EPOCHREALTIME 仅在
bash shell 版本 5 及更高版本中可用。可以在 CLI 中输入 bash --version 来查看 bash shell 的版本号。
$ PATH=$PATH:/home/christine/Scripts
$ mytest=(zero one two three four)
$ echo $mytest
zero
$ echo ${mytest[2]}
two
$ echo ${mytest[*]}
zero one two three four
$ mytest[2]=seven
$ echo ${mytest[2]}
seven
$ unset mytest[2]
$ echo ${mytest[*]}
zero one three four
$ echo ${mytest[2]}
$ echo ${mytest[3]}
three
$ unset mytest
$ echo ${mytest[*]}
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
...
rich:x:500:500:Rich Blum:/home/rich:/bin/bash
...
rich:$1$.FfcK0ns$f1UgiyHQ25wrB/hykCn020:11627:0:99999:7:::
用来向 Linux 系统添加新用户的主要工具是 useradd。该命令可以一次性轻松创建新用户账 户并设置用户的$HOME 目录结构。 useradd 命令使用系统的默认值以及命令行参数来设置用户 账户。要想查看所使用的 Linux 发行版的系统默认值,可以使用加入了-D 选项的 useradd 命令。
# useradd -D
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes
useradd 命令的默认值使用/etc/default/useradd 文件设置。另外,进一步的安全设置在 /etc/login.defs 文件中定义。可以调整这些文件,改变 Linux 系统默认的安全行为。
-D 选项显示了在命令行中创建新用户账户时, 如果不明确指明具体值, useradd 命令所使用的默认值。这些默认值的含义如下。
useradd 命令允许管理员创建默认的 H O M E 目录配置,然后将其作为创建新用户 HOME 目录配置,然后将其作为创建新用户 HOME目录配置,然后将其作为创建新用户HOME 目录的模板。这样就能自动在每个新用户的$HOME 目录里放置默认的系统文件了。
/etc/skel 目录下是bash shell 环境的标准启动文件。系统会自动将这些默认文件复制到你创建的每个用户的$HOME 目录。
对很多 Linux 发行版而言, useradd 命令默认并不创建 H O M E 目录,但是 − m 命令行选项会使其创建 HOME 目录,但是-m 命令行选项会使其创建 HOME目录,但是−m命令行选项会使其创建HOME 目录。可以在/etc/login.defs 文件中更改该行为。
用户账户管理命令需要以 root 用户账户登录或者通过 sudo 命令运行。
要想在创建新用户时改变默认值或默认行为,可以使用相应的命令行选项(但如果总是需要改动某个值, 则最好还是修改一下系统默认值),useradd 命令行选项如下:
| 选项 | 描述 |
|---|---|
| -c comment | 给新用户添加备注 |
| -d home_dir | 为主目录指定一个名字(如果不想用登录名作为主目录名的话) |
| -e expire_date | 用 YYYY-MM-DD 格式指定账户过期日期 |
| -f inactive_days | 指定账户密码过期多少天后禁用该账户; 0 表示密码一过期就立即禁用, -1 表示不使用这个功能 |
| -g initial_group | 指定用户登录组的 GID 或组名 |
| -G group … | 指定除登录组之外用户所属的一个或多个附加组 |
| -k | 必须和-m 一起使用,将/etc/skel 目录的内容复制到用户的$HOME 目录 |
| -m | 创建用户的$HOME 目录 |
| -M | 不创建用户的$HOME 目录,即便默认设置里要求创建 |
| -n | 创建一个与用户登录名同名的新组 |
| -r | 创建系统账户 |
| -p passwd | 为用户账户指定默认密码 |
| -s shell | 指定默认的登录 shell |
| -u uid | 为账户指定一个唯一的 UID |
可以使用-D 选项来修改系统默认的新用户设置。相应的选项如下:
| 选项 | 描述 |
|---|---|
| -b default_home | 修改用户$HOME 目录默认创建的位置 |
| -e expiration_date | 修改新账户的默认过期日期 |
| -f inactive | 修改从密码过期到账户被禁用的默认天数 |
| -g group | 修改默认的组名称或 GID |
| -s shell | 修改默认的登录 shell |
# useradd -D -s /bin/tsch
# useradd -D
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/tsch
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes
#
用户账户修改工具:
| 命令 | 描述 |
|---|---|
| usermod | 修改用户账户字段,还可以指定主要组(primary group )以及辅助组(secondary group )的所属关系 |
| passwd | 修改已有用户的密码 |
| chpasswd | 从文件中读取登录名及密码并更新密码 |
| chage | 修改密码的过期日期 |
| chfn | 修改用户账户的备注信息 |
| chsh | 修改用户账户的默认登录 shell |
usermod 命令是用户账户修改工具中最强大的一个, 提供了修改/etc/passwd 文件中大部分 字段的相关选项,只需指定相应的选项即可。大部分选项与 useradd 命令的选项一样(比如-c 用于修改备注字段, -e 用于修改过期日期, -g 用于修改默认的登录组)。除此之外,还有另外 一些也许能派上用场的选项。
passwd 命令可以方便地修改用户密码。如果只使用 passwd 命令, 则修改的是你自己的密码。系统中的任何用户都能修改自己的密 码,但只有 root 用户才有权限修改别人的密码。
chpasswd 命令可以为系统中的大量用户修改密码。chpasswd 命令能从标准输入自动读取一系列以冒号分隔的登录名和密码对偶(login name and password pair), 自动对密码加密, 然后为用户账户设置密码。你也可以用重定向命令将包含 username:password 对偶的文件重定向给该命令。
# chpasswd < users.txt
chsh、chfn 和 chage 用于修改特定的账户信息。 chsh 命令可以快速修改默认的用户登录 shell。使用时必须用 shell 的全路径名作为参数, 不能只用 shell 名。
# chsh -s /bin/csh test
Changing shell for test.
Shell changed.
chfn 命令提供了在/etc/passwd 文件的备注字段中保存信息的标准方法。chfn 命令会将用于 Unix 的 finger 命令的信息存入备注字段, 而不是简单地写入一些随机文本(比如名字或昵称之 类), 或是干脆将备注字段留空。
finger 命令可以非常方便地查看 Linux 系统的用户信息。出于安全性的考虑,大多数Linux 发行版没有默认安装 finger命令。请注意,安装该命令可能会使你的系统受到攻击漏洞的影响。
如果使用 chfn 命令时不加任何选项,则会询问你要将哪些内容写入备注字段:
chage 命令可用于帮助管理用户账户的有效期。相关选项如下:
| 选项 | 描述 |
|---|---|
| -d | 设置自上次修改密码后的天数 |
| -E | 设置密码过期日期 |
| -I | 设置密码过期多少天后锁定账户 |
| -m | 设置更改密码的最小间隔天数 |
| -M | 设置密码的最大有效天数 |
| -W | 设置密码过期前多久开始出现提醒信息 |
chage 命令的日期值可以用下面两种方式中的任意一种表示:
chage 命令的一个好用的功能是设置账户的过期日期。有了它,就可以创建在特定日期自动过期的临时用户,再也不用操心删除用户了。过期的账户跟锁定的账户类似:账户仍然存在,但用户无法用其登录。
root:x:0:root
bin:x:1:root,bin,daemon
...
rich:x:500:
...
$ ls -l
total 68
-rw-rw-r-- 1 rich rich 50 2010-09-13 07:49 file1.gz
...
-rwxrwxr-x 1 rich rich 4882 2010-09-18 13:58 myprog
$ touch newfile
$ ls -al newfile
-rw-r--r-- 1 rich rich 0 Sep 20 19:16 newfile
$ umask
0022
| 权限 | 二进制值 | 八进制值 | 描述 |
|---|---|---|---|
| — | 000 | 0 | 没有任何权限 |
| –x | 001 | 1 | 只有执行权限 |
| -w- | 010 | 2 | 只有写入权限 |
| -wx | 011 | 3 | 有写入和执行权限 |
| r– | 100 | 4 | 只有读取权限 |
| r-x | 101 | 5 | 有读取和执行权限 |
| rw- | 110 | 6 | 有读取和写入权限 |
| rwx | 111 | 7 | 有全部权限(读取、写入和执行) |
$ umask 026
$ touch newfile2
$ ls -l newfile2
-rw-r----- 1 rich rich 0 Sep 20 19:46 newfile2
chmod options mode file
[ugoa...][[+-=][rwxXstugo...]
$ chmod o+r newfile
$ ls -l newfile
-rwxrw-r-- 1 rich rich 0 Sep 20 19:16 newfile
$ chmod u-x newfile
$ ls -l newfile
-rw-rw-r-- 1 rich rich 0 Sep 20 19:16 newfile
chown options owner[.group] file
// 可以使用登录名或 UID 来指定文件的新属主:
# chown dan newfile
# ls -l newfile
-rw-rw-r-- 1 dan rich 0 Sep 20 19:16 newfile
// chown 命令也支持同时修改文件的属主和属组:
# chown dan.shared newfile
# ls -l newfile
-rw-rw-r-- 1 dan shared 0 Sep 20 19:16 newfile
// 可以只修改文件的默认属组:
# chown .rich newfile
# ls -l newfile
-rw-rw-r-- 1 dan rich 0 Sep 20 19:16 newfile
// 如果你的 Linux 系统使用与用户登录名相同的组名,则可以同时修改二者:
# chown test. newfile
# ls -l newfile
-rw-rw-r-- 1 test test 0 Sep 20 19:16 newfile
// shared 组的任意成员都可以写入该文件:
$ chgrp shared newfile
$ ls -l newfile
-rw-rw-r-- 1 rich shared 0 Sep 20 19:16 newfile
Linux 系统中共享文件的方法是创建组。
创建新文件时, Linux 会用默认的UID 和 GID来给文件分配权限。要想让其他用户也能访问文件,要么修改所有用户一级的安全权限, 要么给文件分配一个包含其他用户的新默认属组。
如果想在大范围内创建并共享文件, 这会很烦琐。幸好有一种简单的解决方法。Linux 为每个文件和目录存储了 3 个额外的信息位。
SGID 位对文件共享非常重要。启用 SGID 位后,可以强制在共享目录中创建的新文件都属 于该目录的属组,这个组也就成了每个用户的属组。
SGID 位会强制某个目录下新建文件或目录都沿用其父目录的属组,而不是创建这些文件的用户的属组。这便于系统用户之间共享文件。
可以通过 chmod 命令设置 SGID,将其添加到标准 3 位八进制值之前(组成 4 位八进制值),或者在符号模式下用符号 s。
SUID 、SGID 和粘滞位的八进制值
| 二进制值 | 八进制值 | 描述 |
|---|---|---|
| 000 | 0 | 清除所有位 |
| 001 | 1 | 设置粘滞位 |
| 010 | 2 | 设置 SGID 位 |
| 011 | 3 | 设置 SGID 位和粘滞位 |
| 100 | 4 | 设置 SUID 位 |
| 101 | 5 | 设置 SUID 位和粘滞位 |
| 110 | 6 | 设置 SUID 位和 SGID 位 |
| 111 | 7 | 设置所有位 |
要创建一个共享目录,使目录中的所有新文件都沿用目录的属组,只需设置该目录的 SGID 位。
$ mkdir testdir
$ ls -l
drwxrwxr-x 2 rich rich 4096 Sep 20 23:12 testdir/
$ chgrp shared testdir
$ chmod g+s testdir
$ ls -l
drwxrwsr-x 2 rich shared 4096 Sep 20 23:12 testdir/
$ umask 002
$ cd testdir
$ touch testfile
$ ls -l
total 0
-rw-rw-r-- 1 rich shared 0 Sep 20 23:13 testfile
$ touch test
$ ls -l
total 0
-rw-r----- 1 rich rich 0 Apr 19 17:33 test
$ getfacl test
# file: test
# owner: rich
# group: rich
user::rw-
group::r--
other::---
$
setfacl [options] rule filenames
// 为 test 文件添加了 sales 组的读写权限。
$ setfacl -m g:sales:rw test
$ ls -l
total 0
-rw-rw----+ 1 rich rich 0 Apr 19 17:33 test
// 可以再次使用 getfacl 命令查看 ACL。
$ getfacl test
# file: test
# owner: rich
# group: rich
user::rw-
group::r--
group:sales:rw-
mask::rw-
other::---
// 要想删除权限,可以使用-x 选项:
$ setfacl -x g:sales test
$ getfacl test
# file: test
# owner: rich
# group: rich
user::rw-
group::r--
mask::r--
other::---
$ sudo setfacl -m d:g:sales:rw /sales
Linux 支持多种文件系统。每种文件系统都在存储设备上实现了虚拟目录结构,只是特性略有不同。
Linux 操作系统最初引入的文件系统叫作扩展文件系统(extended filesystem,简称 ext),它为 Linux 提供了一个基本的类 Unix 文件系统,使用虚拟目录处理物理存储设备并在其中以固定大小的磁盘块(fixed-length block)形式保存数据。
ext 文件系统使用i 节点(inode)跟踪存储在虚拟目录中文件的相关信息。 i 节点系统在每个 物理存储设备中创建一个单独的表(称作 i 节点表)来保存文件信息。虚拟目录中的每个文件在 i 节点表中有对应的条目。ext 文件系统名称中的 extended 部分得名自其所跟踪的每个文件的额外 数据,包括以下内容:
| 方法 | 描述 |
|---|---|
| 数据模式 | i 节点和文件数据都会被写入日志;数据丢失风险低,但性能差 |
| 有序模式 | 只有 i 节点数据会被写入日志,直到文件数据被成功写入后才会将其删除;在性能和安全性之间取得了良好的折中 |
| 回写模式 | 只有 i 节点数据会被写入日志,但不控制文件数据何时写入;数据丢失风险高,但仍好于不用日志 |
ext3 文件系统是 ext2 的后续版本, 支持最大 2 TB 的文件,能够管理 32 TB 大小的分区。在 默认情况下, ext3 采用有序模式的日志方法, 不过也可以通过命令行选项改用其他模式。 ext3 文 件系统无法恢复误删的文件,也没有提供数据压缩功能。
作为 ext3 的后续版本, ext4 文件系统最大支持 16 TiB 的文件,能够管理 1 EiB 大小的分区。 在默认情况下, ext4 采用有序模式的日志方法, 不过也可以通过命令行选项改用其他模式。另外 还支持加密、压缩以及单目录下不限数量的子目录。先前的 ext2 和 ext3 也可以作为 ext4 挂载, 以提高性能表现。
JFS 文件系统采用的是有序模式的日志方法,只在日志中保存 i 节点数据,直到文件数据被 写进存储设备后才将其删除。
XFS 文件系统采用回写模式的日志方法, 在提供了高性能的同时也引入了一定的风险, 因为 实际数据并未存进日志文件。
就文件系统而言, 日志技术的替代选择是一种称作写时复制(copy-on-write,COW)的技术。 COW 通过快照(snapshot)兼顾了安全性和性能。在修改数据时, 使用的是克隆或可写快照。修 改过的数据并不会直接覆盖当前数据,而是被放入文件系统中另一个位置。
ZFS 是一个稳定的文件系统,与 Resier4 、Btrfs 和 ext4 势均力敌。它拥有数据完整性验证和自动修复功能,支持最大 16 EB 的文件,能够管理 256 万亿 ZB(256 quadrillion zettabyte)的存储空间。
Stratis 文件系统维护的存储池由一个或多个XFS 文件系统组成,同时还提供与传统的卷管理文件系统(比如 ZFS 和 Btrfs)相似的 COW 功能。
创建分区
# whoami
root
# fdisk /dev/sda
Welcome to fdisk (util-linux 2.32.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help):
| 命令 | 描述 |
|---|---|
| a | 设置活动分区标志 |
| b | 编辑 BSD Unix 系统使用的标签 |
| c | 设置 DOS 兼容标志 |
| d | 删除分区 |
| g | 创建新的空 GTP 分区表 |
| G | 创建 IRIX( SGI )分区表 |
| l | 显示可用的分区类型 |
| m | 显示命令菜单 |
| n | 添加一个新分区 |
| o | 创建新的空 DOS 分区表 |
| p | 显示当前分区表 |
| q | 退出,不保存更改 |
| s | 为 Sun Unix 系统创建一个新标签 |
| t | 修改分区的系统 ID |
| u | 修改显示单元 |
| v | 验证分区表 |
| w | 将分区表写入磁盘并退出 |
| x | 附加功能(仅供专家使用) |
| 工具 | 用途 |
|---|---|
| mkefs | 创建 ext 文件系统 |
| mke2fs | 创建 ext2 文件系统 |
| mkfs.ext3 | 创建 ext3 文件系统 |
| mkfs.ext4 | 创建 ext4 文件系统 |
| mkreiserfs | 创建 ReiserFS 文件系统 |
| jfs_mkfs | 创建 JFS 文件系统 |
| mkfs.xfs | 创建 XFS 文件系统 |
| mkfs.zfs | 创建 ZFS 文件系统 |
| mkfs.btrfs | 创建 Btrfs 文件系统 |
$ type mkfs.ext4
mkfs.ext4 is /usr/sbin/mkfs.ext4
$ type mkfs.btrfs
-bash: type: mkfs.btrfs: not found
$ sudo mkfs.ext4 /dev/sdb1
[sudo] password for christine:
mke2fs 1.44.6 (5-Mar-2019)
Creating filesystem with 524032 4k blocks and 131072 inodes
[...]
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done
$ mkdir /home/christine/part
$ sudo mount -t ext4 /dev/sdb1 /home/christine/part
[sudo] password for christine:
$ lsblk -f /dev/sdb
NAME FSTYPE LABEL UUID MOUNTPOINT
sdb
⌙ sdb1 ext4 a8d1d[...] /home/christine/part
fsck options filesystem
| 选项 | 描述 |
|---|---|
| -a | 检测到错误时自动修复文件系统 |
| -A | 检查/etc/fstab 文件中列出的所有文件系统 |
| -N | 不进行检查,只显示要检查的内容 |
| -r | 出现错误时进行提示 |
| -R | 使用-A 选项时跳过根文件系统 |
| -t | 指定要检查的文件系统类型 |
| -V | 在检查时产生详细输出 |
| -y | 检测到错误时自动修复文件系统 |
| 命令 | 功能 |
|---|---|
| Vgextend | 将 PV 加入 VG |
| Vgreduce | 从 VG 中删除 PV |
| lvextend | 扩大 LV 的容量 |
| lvreduce | 收缩 LV 的容量 |
软件包管理系统使用数据库来记录下列内容:
软件包存储在称为仓库(repository)的服务器上, 可以利用本地 Linux 系统中的软件包管理 器通过 Internet访问,在其中搜索新的软件包,或是更新系统中已安装的软件包。
软件包通常存在依赖关系,为了能够正常运行,被依赖的包必须提前安装。软件包管理器会检测这些依赖关系并提前安装好所有额外的软件包。
Linux 中广泛使用的两种主要的软件包管理系统基础工具是 dpkg 和 rpm。
apt [options] command
$ apt --installed list
apt show package_name
$ apt show zsh
dpkg -L package_name
$ dpkg -L acl
dpkg --search absolute_file_name
$ dpkg --search /bin/getfacl
acl: /bin/getfacl // 表明文件 getfacl属于 acl 软件包
apt search package_name
$ apt --names-only search zsh
apt install package_name
$ sudo apt install zsh
apt upgrade
apt full-upgrade
$ sudo apt purge zsh
$ sudo apt autoremove
deb (or deb-src) address distribution_name package_type_list
和基于 Debian 的发行版类似,基于 Red Hat 的系统有以下几种前端工具:
dnf 命令的语法为:
dnf [options] [...]
// 要找出系统中已安装的软件包,可以使用下列命令:
dnf list installed
dnf list installed > installed_software
要想查找特定软件包的详细信息,dnf 除了会给出软件包的详细描述,它还可以告诉你软件包是否已经安装:
$ dnf list xterm
$ dnf list installed xterm
如果需要找出文件系统中的某个文件是由哪个软件包安装的,只需输入以下命令:
dnf provides file_name
$ dnf provides /usr/bin/gzip
Last metadata expiration check: 0:12:06 ago on Sat 16 May 2020 12:10:24 PM EDT.
gzip-1.10-1.fc31.x86_64 : The GNU data compression program
Repo : @System
Matched from:
Filename : /usr/bin/gzip
gzip-1.10-1.fc31.x86_64 : The GNU data compression program
Repo : fedora
Matched from:
Filename : /usr/bin/gzip
使用 dnf 安装软件
dnf install package_name
$ sudo dnf install zsh
dnf list upgrades
dnf upgrade package_name
dnf upgrade
dnf remove package_name
dnf clean all
dnf repoquery --deplist package_name
# dnf repoquery --deplist xterm
dnf repolist
$ snap version
$ snap list
$ snap find solitaire
$ snap info solitaire
$ sudo snap install solitaire
$ sudo snap remove solitaire
$ flatpak list
$ flatpak search solitaire
Name Description Application ID Version Branch Remotes
Aisleriot Solitaire org.gnome.Aisleriot stable fedora
$ sudo flatpak install org.gnome.Aisleriot
$ flatpak list
Name Application ID Version Branch Installation
Aisleriot Solitaire org.gnome.Aisleriot stable system
$ sudo flatpak uninstall org.gnome.Aisleriot
// 首先,需要将 sysstat 的 tarball 下载到你的 Linux 系统中。尽管通常能在各种 Linux 网站 上找到 sysstat,但最好直接到程序的官方站点下载。
$ tar -Jxvf sysstat-12.3.3.tar.xz
$ cd sysstat-12.3.3
$ ls
// 在目录的列表中,应该能看到 README 文件或 INSTALL 文件。务必阅读这些文件,其中写明了软件安装所需的操作步骤。
// 运行 configure 工具, 检查你的 Linux,确保拥有 合适的能够编译源代码的编译器,以及正确的库依赖关系
// 如果有问题, 则 configure 会显示错误消息,说明缺失了哪些东西。
$ ./configure
// 用 make 命令来构建各种二进制文件。 make 命令会编译源代码,然后由链接器生 成最终的可执行文件。和 configure 命令一样, make 命令会在编译和链接所有源代码文件的 过程中产生大量的输出:
$ make
// make 命令结束后, 可运行的sysstat 程序就出现在目录中了。但是从这个目录中运行程序有 点儿不方便。你希望将其安装在 Linux 系统的常用位置。为此, 必须以 root 用户身份登录(或者 使用 sudo 命令), 然后使用 make 命令的 install 选项:
# make instal
$ sudo apt install vim
| 命令 | 描述 |
|---|---|
| x | 删除光标当前所在位置的字符 |
| dd | 删除光标当前所在行 |
| dw | 删除光标当前所在位置的单词 |
| d$ | 删除光标当前所在位置至行尾的内容 |
| J | 删除光标当前所在行结尾的换行符(合并行) |
| u | 撤销上一个编辑命令 |
| a | 在光标当前位置后追加数据 |
| A | 在光标当前所在行结尾追加数据 |
| r char | 用 char 替换光标当前所在位置的单个字符 |
| R text | 用 text 覆盖光标当前所在位置的内容,直到按下 ESC 键 |
略。
略。
略。
略。
#!/bin/bash
$ ./test1
bash: ./test1: Permission denied
$ ls -l test1
-rw-r--r-- 1 user user 73 Jun 02 15:36 test1
$ chmod u+x test1
$ ./test1
// 执行结果打印
$ echo "This is a test to see if you're paying attention"
This is a test to see if you're paying attention
$ echo 'Rich says "scripting is easy".'
Rich says "scripting is easy".
echo The time and date are:
date
改成:
echo -n "The time and date are:"
变量允许在 shell 脚本中临时存储信息,以便同脚本中的其他命令一起使用。
环境变量
#!/bin/bash
# display user information from the system.
echo "User info for userid: $USER"
echo UID: $UID
echo HOME: $HOME
$ echo "The cost of the item is $15"
The cost of the item is 5
$ echo "The cost of the item is \$15"
The cost of the item is $15
var1=10
var2=-57
var3=testing
var4="still more testing"
testing=`date`
testing=$(date)
#!/bin/bash
# copy the /usr/bin directory listing to a log file
today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
command > outputfile
command < inputfile
command << marker
data
marker
$ wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
3 9 42
command1 | command2
$ ls -al | more
在 shell 脚本中,执行数学运算有两种方式:
expr 命令
| 运算符 | 描述 |
|---|---|
| ARG1 | ARG2 | 如果 ARG1 既不为 null 也不为 0 ,就返回 ARG1;否则,返回 ARG2 |
| ARG1 & ARG2 | 如果 ARG1 和 ARG2 都不为 null 或 0 ,就返回 ARG1;否则,返回 0 |
| ARG1 < ARG2 | 如果 ARG1 小于 ARG2,就返回 1 ;否则,返回 0 |
| ARG1 <= ARG2 | 如果 ARG1 小于或等于 ARG2,就返回 1 ;否则,返回 0 |
| ARG1 = ARG2 | 如果 ARG1 等于 ARG2,就返回 1 ;否则,返回 0 |
| ARG1 != ARG2 | 如果 ARG1 不等于 ARG2,就返回 1 ;否则,返回 0 |
| ARG1 >= ARG2 | 如果 ARG1 大于或等于 ARG2,就返回 1 ;否则,返回 0 |
| ARG1 > ARG2 | 如果 ARG1 大于 ARG2,就返回 1 ;否则,返回 0 |
| ARG1 + ARG2 | 返回 ARG1 和 ARG2 之和 |
| ARG1 - ARG2 | 返回 ARG1 和 ARG2 之差 |
| ARG1 * ARG2 | 返回 ARG1 和 ARG2 之积 |
| ARG1 / ARG2 | 返回 ARG1 和 ARG2 之商 |
| ARG1 % ARG2 | 返回 ARG1 和 ARG2 之余数 |
| STRING : REGEXP | 如果 REGEXP 模式匹配 STRING,就返回该模式匹配的内容 |
| match STRING REGEXP | 如果 REGEXP 模式匹配 STRING,就返回该模式匹配的内容 |
| substr STRING POS LENGTH | 返回起始位置为 POS (从 1 开始计数)、长度为 LENGTH 的子串 |
| index STRING CHARS | 返回 CHARS 在字符串 STRING 中所处的位置;否则,返回 0 |
| length STRING | 返回字符串 STRING 的长度 |
$ var1=$[1 + 5]
$ echo $var1
6
$ var2=$[$var1 * 2]
$ echo $var2
12
$ bc -q
3.44 / 5
0
scale=4
3.44 / 5
.6880
quit
$ bc -q
var1=10
var1 * 4
40
var2 = var1 / 5
print var2
2
quit
variable=$(echo "options; expression" | bc)
#!/bin/bash
var1=$(echo " scale=4; 3.44 / 5" | bc)
echo The answer is $var1
variable=$(bc << EOF
options
statements
expressions
EOF
)
$ cat test12
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale = 4
a1 = ( $var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)
echo The final answer for this mess is $var5
$
Linux 提供了专门的变量 ? 来保存最后一个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用 ?来保存最后一个已执行命令的退出状态码。对于需要进行检查的 命令,必须在其运行完毕后立刻查看或使用 ?来保存最后一个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用?变量。这是因为该变量的值会随时变成由 shell 所 执行的最后一个命令的退出状态码。
按照惯例,对于成功结束的命令, 其退出状态码是 0。对于因错误而结束的命令,其退出状态码是一个正整数。
无效命令会返回退出状态码 127。
Linux 退出状态码
| 状态码 | 描述 |
|---|---|
| 0 | 命令成功结束 |
| 1 | 一般性未知错误 |
| 2 | 不适合的 shell 命令 |
| 126 | 命令无法执行 |
| 127 | 没找到命令 |
| 128 | 无效的退出参数 |
| 128+x | 与 Linux 信号 x 相关的严重错误 |
| 130 | 通过 Ctrl+C 终止的命令 |
| 255 | 正常范围之外的退出状态码 |
退出状态码 126 表明用户没有执行命令的正确权限
另一个常见错误是给命令提供了无效参数,这会产生一般性的退出状态码 1,表明在命令中发生了未知错误。
$ cat test13
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[ $var1 + $var2 ]
echo The answer is $var3
exit 5
$
$ chmod u+x test13
$ ./test13
The answer is 40
$ echo $?
5
$
$date -d "Jan 1, 2020" +%s
1577854800
$
if command
then
commands
fi
if command; then
commands
fi
if command
then
commands
else
commands
fi
if command1
then
commands
else
if command2
then
more commands
fi
fi
if command1
then
commands
elif command2
then
more commands
fi
if command1
then
commands
elif command2
then
more commands1
else
more commands2
fi
if command1
then
command set 1
elif command2
then
command set 2
elif command3
then
command set 3
elif command4
then
command set 4
fi
test condition
if test condition
then
commands1
else
commands2
fi
$ cat test6.sh
#!/bin/bash
# testing if a variable has content
#
my_variable="Full" // 情况二:my_variable=""
#
if test $my_variable
then
echo "The my_variable variable has content and returns a True."
echo "The my_variable variable content is: $my_variable"
else
echo "The my_variable variable doesn't have content,"
echo "and returns a False."
fi
$
$ ./test6.sh
The my_variable variable has content and returns a True.
The my_variable variable content is: Full
$
if [ condition ]
then
commands
fi
test 命令的数值比较功能:
| 比较 | 描述 |
|---|---|
| n1 -eq n2 | 检查 n1 是否等于 n2 |
| n1 -ge n2 | 检查 n1 是否大于或等于 n2 |
| n1 -gt n2 | 检查 n1 是否大于 n2 |
| n1 -le n2 | 检查 n1 是否小于或等于 n2 |
| n1 -lt n2 | 检查 n1 是否小于 n2 |
| n1 -ne n2 | 检查 n1 是否不等于 n2 |
数值条件测试可用于数字和变量。
if [ $value1 -gt 5 ]
if [ $value1 -eq $value2 ]
对于条件测试, bash shell 只能处理整数。尽管可以将浮点值用于某些命令(比如 echo),但它们在条件测试下无法正常工作。
条件测试还允许比较字符串值。
test 命令的字符串比较功能:
| 比较 | 描述 |
|---|---|
| str1 = str2 | 检查 str1 是否和 str2 相同 |
| str1 != str2 | 检查 str1 是否和 str2 不同 |
| str1 < str2 | 检查 str1 是否小于 str2 |
| str1 > str2 | 检查 str1 是否大于 str2 |
| -n str1 | 检查 str1 的长度是否不为 0 |
| -z str1 | 检查 str1 的长度是否为 0 |
字符串相等性
if [ $testuser = christine ]
if [ $testuser != christine ]
字符串顺序
if [ $string1 > $string2 ] // 没有报错,但结果与预期结果不符。需要使用反斜线(\)正确地转义大于号。
if [ $string1 \> $string2 ]
sort 命令处理大写字母的方法刚好与 test 命令相反:
test 命令和测试表达式使用标准的数学比较符号来表示字符串比较,而用文本代码来表示数值比较。
字符串大小
if [ -n $string1 ]
if [ -z $string2 ]
空变量和未初始化的变量会对 shell 脚本测试造成灾难性的影响。如果不确定变量的内容,那么最好在将其用于数值或字符串比较之前先通过-n 或-z 来测试一下变量是否为空。
比较测试允许测试 Linux 文件系统中文件和目录的状态。
test 命令的文件比较功能
| 比较 | 描述 |
|---|---|
| -d file | 检查 file 是否存在且为目录 |
| -e file | 检查 file 是否存在 |
| -f file | 检查 file 是否存在且为文件 |
| -r file | 检查 file 是否存在且可读 |
| -s file | 检查 file 是否存在且非空 |
| -w file | 检查 file 是否存在且可写 |
| -x file | 检查 file 是否存在且可执行 |
| -O file | 检查 file 是否存在且属当前用户所有 |
| -G file | 检查 file 是否存在且默认组与当前用户相同 |
| file1 -nt file2 | 检查 file1 是否比 file2 新 |
| file1 -ot file2 | 检查 file1 是否比 file2 旧 |
检查目录:-d 测试会检查指定的目录是否存在于系统中。如果打算将文件写入目录或是准备切换到某个目录, 那么先测试一下总是件好事:
#!/bin/bash
jump_directory=/home/Torfa
if [ -d $jump_directory ]
then
cd $jump_directory
ls
else
echo "The $jump_directory directory does NOT exist."
fi
检查对象是否存在:-e 测试允许在使用文件或目录前先检查其是否存在:
location=$HOME
file_name="sentinel"
if [ -d $location ]
if [ -e $location/$file_name ]
检查文件:-e 测试可用于文件和目录。如果要确定指定对象为文件,那就必须使用-f 测试:
object_name=$HOME // object_name=$HOME/sentinel
if [ -e $object_name ]
if [ -f $object_name ]
检查是否可读:在尝试从文件中读取数据之前,最好先使用-r 测试检查一下文件是否可读:
pwfile=/etc/shadow
if [ -f $pwfile ]
if [ -r $pwfile ]
检查空文件:应该用-s 测试检查文件是否为空, 尤其是当你不想删除非空文件时。要当心,如果-s 测试 成功,则说明文件中有数据:
file_name=$HOME/sentinel
if [ -f $file_name ]
if [ -s $file_name ]
检查是否可写:-w 测试可以检查是否对文件拥有可写权限:
item_name=$HOME/sentinel
if [ -f $item_name ]
if [ -w $item_name ]
检查文件是否可以执行:-x 测试可以方便地判断文件是否有执行权限。虽然可能大多数命令用不到它,但如果想在 shell 脚本中运行大量程序, 那就得靠它了:
item_name=$HOME/scripts/can-I-write-to-it.sh
if [ -x $item_name ]
检查所有权:-O 测试可以轻松地检查你是否是文件的属主:
if [ -O /etc/passwd ]
检查默认属组关系:-G 测试可以检查文件的属组, 如果与用户的默认组匹配,则测试成功。 -G 只会检查默认组而非用户所属的所有组:
if [ -G $HOME/TestGroupFile ]
检查文件日期:
if [ $HOME/Downloads/games.rpm -nt $HOME/software/games.rpm ]
(command)
echo $BASH_SUBSHELL
if (echo $BASH_SUBSHELL)
then
xxx
else
xxx
fi
双括号命令允许在比较过程中使用高级数学表达式。 test 命令在进行比较的时候只能使用 简单的算术操作。双括号命令提供了更多的数学符号。双括号命令的格式如下:
(( expression ))
除了test 命令使用的标准数学运算符,下表还列出了双括号中可用的其他运算符。双括号命令符号:
| 符号 | 描述 |
|---|---|
| val++ | 后增 |
| val– | 后减 |
| ++val | 先增 |
| –val | 先减 |
| ! | 逻辑求反 |
| ~ | 位求反 |
| ** | 幂运算 |
| << | 左位移 |
| >> | 右位移 |
| & | 位布尔 AND |
| | | 位布尔 OR |
| && | 逻辑 AND |
| || | 逻辑 OR |
双括号命令既可以在 if 语句中使用,也可以在脚本中的普通命令里用来赋值。
val1=10
if (( $val1 ** 2 > 90 ))
then
(( val2 = $val1 ** 2 ))
fi
[[ expression ]]
if [[ $BASH_VERSION == 5.* ]]
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
$ cat ShortCase.sh
#!/bin/bash
# Using a short case statement
#
case $USER in
rich | christine)
echo "Welcome $USER"
echo "Please enjoy your visit.";;
barbara | tim)
echo "Hi there, $USER"
echo "We're glad you could join us.";;
testing)
echo "Please log out when done with test.";;
*)
echo "Sorry, you are not allowed here."
esac
$
$ ./ShortCase.sh
Welcome christine
Please enjoy your visit.
$
if (which yum &> /dev/null) // 检查是否有 yum 工具
for var in list
do
commands
done
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo The next state is $test
done
echo "The last state we visited was $test"
for test in I don't know if this'll work
for test in I don\'t know if "this'll" work
for test in Nevada New Hampshire New Mexico New York North Carolina
for test in Nevada "New Hampshire" "New Mexico" "New York"
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"
for state in $list
file="states.txt"
for state in $(cat $file)
$ cat states.txt
Alabama
Alaska
Arizona
$ ./test5
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
IFS=$'\n'
file="states.txt"
IFS=$'\n'
for state in $(cat $file)
IFS.OLD=$IFS
IFS=$'\n'
<在代码中使用新的 IFS 值>
IFS=$IFS.OLD
IFS=:
IFS=$'\n:;"'
for file in /home/rich/test/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
for file in /home/rich/.b* /home/rich/badtest
for (( variable assignment ; condition ; iteration process ))
for (( i=1; i <= 10; i++ ))
do
echo "The next number is $i"
done
for (( a=1, b=10; a <= 10; a++, b-- ))
while test command
do
other commands
done
var1=10
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done
var1=10
while echo $var1
[ $var1 -ge 0 ]
do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
until test commands
do
other commands
done
$ cat test12
#!/bin/bash
# using the until command
var1=100
until [ $var1 -eq 0 ]
do
echo $var1
var1=$[ $var1 - 25 ]
done
var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 25 ]
done
#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry –"
IFS=:
for value in $entry
do
echo " $value"
done
done
$
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Iteration number: $var1"
done
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -eq 5 ]
then
break
fi
echo " Inner loop: $b"
done
done
break n
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -gt 4 ]
then
break 2
fi
echo " Inner loop: $b"
done
done
for (( var1 = 1; var1 < 15; var1++ ))
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number: $var1"
done
continue n
for file in /home/rich/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif
echo "$file is a file"
fi
done > output.txt
for state in "North Dakota" Connecticut Illinois Alabama Tennessee
do
echo "$state is the next place to go"
done | sort
实战演练-查找可执行文件
$ cat test25
#!/bin/bash
# finding files in the PATH
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done
实战演练-创建多个用户账户
$ cat test26
#!/bin/bash
# process new user accounts
input="users.csv"
while IFS=',' read -r loginname name
do
echo "adding $loginname"
useradd -c "$name" -m $loginname
done < "$input"
$ cat users.csv
rich,Richard Blum
christine,Christine Bresnahan
bash shell 提供了一些不同的方法来从
用户处获取数据,包括命令行参数(添加在命令后的数据)、命令行选项(可改变命令行为的单个字母) 以及直接从键盘读取输入。
命令行参数允许运行脚本时在命令行中添加数据:
$ ./addem 10 30
读取参数
$ cat positional1.sh
#!/bin/bash
# Using one command-line parameter
#
factorial=1
for (( number = 1; number <= $1; number++ ))
do
factorial=$[ $factorial * $number ]
done
echo The factorial of $1 is $factorial
exit
$
$ cat positional10.sh
#!/bin/bash
# Handling lots of command-line parameters
#
product=$[ ${10} * ${11} ]
echo The tenth parameter is ${10}.
echo The eleventh parameter is ${11}.
echo The product value is $product.
exit
$
$ ./positional10.sh 1 2 3 4 5 6 7 8 9 10 11 12
The tenth parameter is 10.
The eleventh parameter is 11.
The product value is 110.
$ ./positional0.sh
This script name is ./positional0.sh .
$ $HOME/scripts/positional0.sh
This script name is /home/christine/scripts/positional0.sh .
name=$(basename $0)
echo This script name is $name.
在 bash shell 中有一些跟踪命令行参数的特殊变量:
参数统计
if [ $# -eq 1 ]
if [ $# -ne 2 ]
echo The last parameter is ${$#} // 错误写法
echo The last parameter is ${!#} // 正确写法
$ cat grabbingallparams.sh
#!/bin/bash
# Testing different methods for grabbing all the parameters
#
echo
echo "Using the \$* method: $*"
echo
echo "Using the \$@ method: $@"
echo
exit
$
$ ./grabbingallparams.sh alpha beta charlie delta
Using the $* method: alpha beta charlie delta
Using the $@ method: alpha beta charlie delta
$
$ cat grabdisplayallparams.sh
#!/bin/bash
# Exploring different methods for grabbing all the parameters
#
echo
echo "Using the \$* method: $*"
count=1
for param in "$*"
do
echo "\$* Parameter #$count = $param"
count=$[ $count + 1 ]
done
#
echo
echo "Using the \$@ method: $@"
count=1
for param in "$@"
do
echo "\$@ Parameter #$count = $param"
count=$[ $count + 1 ]
done
echo
exit
$ ./grabdisplayallparams.sh alpha beta charlie delta
Using the $* method: alpha beta charlie delta
$* Parameter #1 = alpha beta charlie delta
Using the $@ method: alpha beta charlie delta
$@ Parameter #1 = alpha
$@ Parameter #2 = beta
$@ Parameter #3 = charlie
$@ Parameter #4 = delta
$
$ cat shiftparams.sh
#!/bin/bash
# Shifting through the parameters
#
echo
echo "Using the shift method:"
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
done
echo
exit
$
$ ./shiftparams.sh alpha bravo charlie delta
Using the shift method:
Parameter #1 = alpha
Parameter #2 = bravo
Parameter #3 = charlie
Parameter #4 = delta
$ cat bigshiftparams.sh
#!/bin/bash
# Shifting multiple positions through the parameters
#
echo
echo "The original parameters: $*"
echo "Now shifting 2..."
shift 2
echo "Here's the new first parameter: $1"
echo
exit
$
$ ./bigshiftparams.sh alpha bravo charlie delta
The original parameters: alpha bravo charlie delta
Now shifting 2...
Here's the new first parameter: charlie
$
$ cat extractoptions.sh
#!/bin/bash
# Extract command-line options
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
*) echo "$1 is not an option" ;;
esac
shift
done
echo
exit
$
$ ./extractoptions.sh -a -b -c -d
Found the -a option
Found the -b option
Found the -c option
-d is not an option
$ cat extractoptionsparams.sh
#!/bin/bash
# Extract command-line options and parameters
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
--) shift
break;;
*) echo "$1 is not an option" ;;
esac
shift
done
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
echo
exit
$
$ cat extractoptionsvalues.sh
#!/bin/bash
# Extract command-line options and values
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param=$2
echo "Found the -b option with parameter value $param"
shift;;
-c) echo "Found the -c option" ;;
--) shift
break;;
*) echo "$1 is not an option" ;;
esac
shift
done
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
exit
$
$ ./extractoptionsvalues.sh -a -b BValue -d
Found the -a option
Found the -b option with parameter value BValue
-d is not an option
$
getopt optstring parameters
$ getopt ab:cd -a -b BValue -cd test1 test2
-a -b BValue -c -d -- test1 test2
$ getopt ab:cd -a -b BValue -cde test1 test2
getopt: invalid option -- 'e'
-a -b BValue -c -d -- test1 test2
$ getopt -q ab:cd -a -b BValue -cde test1 test2
-a -b 'BValue' -c -d -- 'test1' 'test2'
set -- $(getopt -q ab:cd "$@")
set -- $(getopt -q ab:cd "$@")
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param=$2
echo "Found the -b option with parameter value $param"
shift;;
-c) echo "Found the -c option" ;;
--) shift
break;;
*) echo "$1 is not an option" ;;
esac
shift
done
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
exit
getopts optstring variable
$ cat extractwithgetopts.sh
#!/bin/bash
# Extract command-line options and values with getopts
#
echo
while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option with parameter value $OPTARG";;
c) echo "Found the -c option" ;;
*) echo "Unknown option: $opt" ;;
esac
done
exit
$
$ ./extractwithgetopts.sh -ab BValue -c
Found the -a option
Found the -b option with parameter value BValue
Found the -c option
$
$ ./extractwithgetopts.sh -b "BValue1 BValue2" -a
$ ./extractwithgetopts.sh -abBValue
$ ./extractwithgetopts.sh -d
Unknown option: ?
while getopts :ab:cd opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option with parameter value $OPTARG";;
c) echo "Found the -c option" ;;
d) echo "Found the -d option" ;;
*) echo "Unknown option: $opt" ;;
esac
done
#
shift $[ $OPTIND - 1 ]
#
echo
count=1
for param in "$@"
do
echo "Parameter $count: $param"
count=$[ $count + 1 ]
done
exit
| 选项 | 描述 |
|---|---|
| -a | 显示所有对象 |
| -c | 生成计数 |
| -d | 指定目录 |
| -e | 扩展对象 |
| -f | 指定读入数据的文件 |
| -h | 显示命令的帮助信息 |
| -i | 忽略文本大小写 |
| -l | 产生长格式输出 |
| -n | 使用非交互模式(批处理) |
| -o | 将所有输出重定向至指定的文件 |
| -q | 以静默模式运行 |
| -r | 递归处理目录和文件 |
| -s | 以静默模式运行 |
| -v | 生成详细输出 |
| -x | 排除某个对象 |
| -y | 对所有问题回答 yes |
echo -n "Enter your name: "
read name
echo "Hello $name, welcome to my script."
exit
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That means you are over $days days old!"
read -p "Enter your first and last name: " first last
read -p "Enter your name: "
echo
echo "Hello $REPLY, welcome to my script."
if read -t 5 -p "Enter your name: " name
then
echo "Hello $name, welcome to my script."
else
echo
echo "Sorry, no longer waiting for name."
fi
read -n 1 -p "Do you want to continue [Y/N]? " answer
#
case $answer in
Y | y) echo
echo "Okay. Continue on...";;
N | n) echo
echo "Okay. Goodbye"
exit;;
esac
echo "This is the end of the script."
exit
read -s -p "Enter your password: " pass
echo
echo "Your password is $pass"
exit
count=1
cat $HOME/scripts/test.txt | while read line
do
echo "Line $count: $line"
count=$[ $count + 1 ]
done
echo "Finished processing the file."
exit
Linux 用文件描述符来标识每个文件对象。文件描述符是一个非负整数,唯一会标识的是会话中打开的文件。每个进程一次最多可以打开 9 个文件描述符(这个数量并不是固定的)。
出于特殊目的, bash shell 保留了前 3 个文件描述符(0、1 和 2),Linux 的标准文件描述符:
| 文件描述符 | 缩写 | 描述 |
|---|---|---|
| 0 | STDIN | 标准输入 |
| 1 | STDOUT | 标准输出 |
| 2 | STDERR | 标准错误 |
STDIN
STDOUT
STDERR
$ ls -al badfile 2> test4
$ cat test4
ls: cannot access badfile: No such file or directory
$ ls -al test badtest test2 2> test5
-rw-rw-r-- 1 rich rich 158 2020-06-20 11:32 test2
$ cat test5
ls: cannot access test: No such file or directory
ls: cannot access badtest: No such file or directory
$
$ ls -al test test2 test3 badtest 2> test6 1> test7
$ cat test6
ls: cannot access test: No such file or directory
ls: cannot access badtest: No such file or directory
$ cat test7
-rw-rw-r-- 1 rich rich 158 2020-06-20 11:32 test2
-rw-rw-r-- 1 rich rich 0 2020-06-20 11:33 test3
$
$ ls -al test test2 test3 badtest &> test7
$ cat test7
ls: cannot access test: No such file or directory
ls: cannot access badtest: No such file or directory
-rw-rw-r-- 1 rich rich 158 2020-06-20 11:32 test2
-rw-rw-r-- 1 rich rich 0 2020-06-20 11:33 test3
$
在脚本中重定向输出的方法有两种:
临时重定向
echo "This is an error message" >&2
$ cat test8
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"
$
$ ./test8
This is an error
This is normal output
$
$ ./test8 2> test9
This is normal output
$ cat test9
This is an error
$ ./test8 2> test9
This is normal output
$ cat test9
This is an error
$
$ cat test10
#!/bin/bash
# redirecting all output to a file
exec 1>testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"
$ ./test10
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
$
$ cat test11
#!/bin/bash
# redirecting output to different locations
exec 2>testerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>testout
echo "This output should go to the testout file"
echo "but this should go to the testerror file" >&2
$
$ ./test11
This is the start of the script
now redirecting all output to another location
$ cat testout
This output should go to the testout file
$ cat testerror
but this should go to the testerror file
$
exec 0< testfile
$ cat test12
#!/bin/bash
# redirecting file input
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
$ ./test12
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
$
$ cat test13
#!/bin/bash
# using an alternative file descriptor
exec 3>test13out
echo "This should display on the monitor"
echo "and this should be stored in the file" >&3
echo "Then this should be back on the monitor"
$ ./test13
This should display on the monitor
Then this should be back on the monitor
$ cat test13out
and this should be stored in the file
$
exec 3>>test13out
$ cat test14
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1
exec 1>test14out
echo "This should store in the output file"
echo "along with this line."
exec 1>&3
echo "Now things should be back to normal"
$
$ ./test14
Now things should be back to normal
$ cat test14out
This should store in the output file
along with this line.
$
$ cat test15
#!/bin/bash
# redirecting input file descriptors
exec 6<&0
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
Y|y) echo "Goodbye";;
N|n) echo "Sorry, this is the end.";;
esac
$ ./test15
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
Are you done now? y
Goodbye
$
$ cat test16
#!/bin/bash
# testing input/output file descriptor
exec 3<> testfile
read line <&3
echo "Read: $line"
echo "This is a test line" >&3
$ cat testfile
This is the first line.
This is the second line.
This is the third line.
$ ./test16
Read: This is the first line.
$ cat testfile
This is the first line.
This is a test line
ine.
This is the third line.
$
exec 3>&-
lsof 命令会列出整个 Linux 系统打开的所有文件描述符,这包括所有后台进程以及登录用户打开的文件。
有大量的命令行选项和参数可用于过滤 lsof 的输出。最常用的选项包括-p 和-d,前者允许指定进程 ID(PID ),后者允许指定要显示的文件描述符编号(多个编号之间以逗号分隔)。
要想知道进程的当前 PID,可以使用特殊环境变量$$(shell 会将其设为当前 PID )。-a 选项 可用于对另外两个选项的结果执行 AND 运算,命令输出如下:
$ /usr/sbin/lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 3344 rich 0u CHR 136,0 2 /dev/pts/0
bash 3344 rich 1u CHR 136,0 2 /dev/pts/0
bash 3344 rich 2u CHR 136,0 2 /dev/pts/0
$
lsof 的默认输出
| 列 | 描述 |
|---|---|
| COMMAND | 进程对应的命令名的前 9 个字符 |
| PID | 进程的 PID |
| USER | 进程属主的登录名 |
| FD | 文件描述符编号以及访问类型(r 代表读, w 代表写, u 代表读/写) |
| TYPE | 文件的类型( CHR 代表字符型, BLK 代表块型, DIR 代表目录, REG 代表常规文件) |
| DEVICE | 设备号(主设备号和从设备号) |
| SIZE | 如果有的话,表示文件的大小 |
| NODE | 本地文件的节点号 |
| NAME | 文件名 |
与 STDIN、STDOUT 和 STDERR 关联的文件类型是字符型,因为文件描述符 STDIN、STDOUT 和 STDERR 都指向终端,所以输出文件名就是终端的设备名。这 3 个标准文件都支持读和写。
例子:
$ cat test18
#!/bin/bash
# testing lsof with file descriptors
exec 3> test18file1
exec 6> test18file2
exec 7< testfile
/usr/sbin/lsof -a -p $$ -d0,1,2,3,6,7
$ ./test18
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
test18 3594 rich 0u CHR 136,0 2 /dev/pts/0
test18 3594 rich 1u CHR 136,0 2 /dev/pts/0
test18 3594 rich 2u CHR 136,0 2 /dev/pts/0
18 3594 rich 3w REG 253,0 0 360712 /home/rich/test18file1
18 3594 rich 6w REG 253,0 0 360715 /home/rich/test18file2
18 3594 rich 7r REG 253,0 73 360717 /home/rich/testfile
$
$ cat /dev/null > testfile
$ mktemp testing.XXXXXX
$ ls -al testing*
-rw------- 1 rich rich 0 Jun 20 21:30 testing.UfIi13
tempfile=$(mktemp test19.XXXXXX)
$ mktemp -t test.XXXXXX
/tmp/test.xG3374
tempfile=$(mktemp -t tmp.XXXXXX)
echo "The temp file is located at: $tempfile"
tempdir=$(mktemp -d dir.XXXXXX)
cd $tempdir
tempfile1=$(mktemp temp.XXXXXX)
tempfile2=$(mktemp temp.XXXXXX)
tee filename
$ date | tee testfile
Sun Jun 21 18:56:21 EDT 2020
$ cat testfile
Sun Jun 21 18:56:21 EDT 2020
outfile='members.sql'
IFS=','
while read lname fname address city state zip
do
cat >> $outfile << EOF
INSERT INTO members (lname,fname,address,city,state,zip) VALUES
('$lname', '$fname', '$address', '$city', '$state', '$zip');
EOF
done < ${1}
Linux 利用信号与系统中的进程进行通信。
重温 Linux 信号
Linux 系统和应用程序可以产生超过 30 个信号。在 shell 脚本编程时会遇到的最常见的 Linux 系统信号如下:
| 信号 | 值 | 描述 |
|---|---|---|
| 1 | SIGHUP | 挂起(hang up )进程 |
| 2 | SIGINT | 中断(interrupt)进程 |
| 3 | SIGQUIT | 停止(stop)进程 |
| 9 | SIGKILL | 无条件终止(terminate)进程 |
| 15 | SIGTERM | 尽可能终止进程 |
| 18 | SIGCONT | 继续运行停止的进程 |
| 19 | SIGSTOP | 无条件停止,但不终止进程 |
| 20 | SIGTSTP | 停止或暂停( pause ),但不终止进程 |
在默认情况下, bash shell 会忽略收到的任何 SIGQUIT(3)信号和 SIGTERM(15)信号(因此交互式 shell 才不会被意外终止)。但是,bash shell 会处理收到的所有 SIGHUP(1)信号和 SIGINT(2)信号。
如果收到了 SIGHUP 信号(比如在离开交互式 shell 时), bash shell 就会退出。但在退出之前,它会将 SIGHUP 信号传给所有由该 shell 启动的进程, 包括正在运行的shell 脚本。
随着收到 SIGINT 信号, shell 会被中断。 Linux 内核将不再为 shell 分配 CPU 处理时间。当出现这种情况时, shell 会将 SIGINT 信号传给由其启动的所有进程,以此告知出现的状况。
shell 会将这些信号传给 shell 脚本来处理。而 shell 脚本的默认行为是忽略这些信号,因为可能不利于脚本运行。
$ sleep 60
^Z
[1]+ Stopped sleep 60
$
$ sleep 70
^Z
[2]+ Stopped sleep 70
$
$ exit
logout
There are stopped jobs.
$
$ ps -l
F S UID PID PPID [...] TTY TIME CMD
0 S 1001 1509 1508 [...] pts/0 00:00:00 bash
0 T 1001 1532 1509 [...] pts/0 00:00:00 sleep
0 T 1001 1533 1509 [...] pts/0 00:00:00 sleep
0 R 1001 1534 1509 [...] pts/0 00:00:00 ps
$
trap commands signals
$ cat trapsignal.sh
#!/bin/bash
#Testing signal trapping
#
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
#
echo This is a test script.
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
echo "This is the end of test script."
exit
$
trap "" SIGINT
$ cat trapexit.sh
#!/bin/bash
#Testing exit trapping
#
trap "echo Goodbye..." EXIT
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
$ cat trapmod.sh
#!/bin/bash
#Modifying a set trap
#
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 3 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
trap "echo ' I have modified the trap!'" SIGINT
#
count=1
while [ $count -le 3 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
trap -- SIGINT
$ ps -e
PID TTY TIME CMD
1 ? 00:00:02 systemd
2 ? 00:00:00 kthreadd
3 ? 00:00:00 rcu_gp
4 ? 00:00:00 rcu_par_gp
[...]
2585 pts/0 00:00:00 ps
$
$ ./backgroundscript.sh &
[1] 2595
$
[1]+ Done ./backgroundscript.sh
nohup command
$ nohup ./testAscript.sh &
[1] 1828
$ nohup: ignoring input and appending output to 'nohup.out'
jobs 是作业控制中的关键命令,该命令允许用户查看 shell 当前正在处理的作业。
通过 jobs 命令可以查看分配给 shell 的作业,例如:
$ jobs
[1]+ Stopped ./jobcontrol.sh
[2]- Running ./jobcontrol.sh > jobcontrol.out &
可以使用 jobs 命令的-l 选项(小写字母 l)查看作业的 PID。
$ jobs -l
[1]+ 1580 Stopped ./jobcontrol.sh
[2]- 1603 Running ./jobcontrol.sh > jobcontrol.out &
jobs 命令提供了一些命令行选项:
| 选项 | 描述 |
|---|---|
| -l | 列出进程的 PID 以及作业号 |
| -n | 只列出上次 shell 发出通知后状态发生改变的作业 |
| -p | 只列出作业的 PID |
| -r | 只列出运行中的作业 |
| -s | 只列出已停止的作业 |
如果需要删除已停止的作业, 那么使用 kill 命令向其 PID 发送 SIGKILL(9)信号即可。
$ jobs -l
[1]+ 1580 Stopped ./jobcontrol.sh
$
$ kill -9 1580
[1]+ Killed ./jobcontrol.sh
$
$ ./restartjob.sh
^Z
[1]+ Stopped ./restartjob.sh
$
$ bg
[1]+ ./restartjob.sh &
$
$ jobs
[1]+ Running ./restartjob.sh &
$
$ jobs
$
$ ./restartjob.sh
^Z
[1]+ Stopped ./restartjob.sh
$
$ ./newrestartjob.sh
^Z
[2]+ Stopped ./newrestartjob.sh
$
$ bg 2
[2]+ ./newrestartjob.sh &
$
$ jobs
[1]+ Stopped ./restartjob.sh
[2]- Running ./newrestartjob.sh &
$
$ jobs
[1]+ Stopped ./restartjob.sh
[2]- Running ./newrestartjob.sh &
$
$ fg 2
./newrestartjob.sh
This is the script's end.
$
$ nice -n 10 ./jobcontrol.sh > jobcontrol.out &
[2] 16462
$
$ ps -p 16462 -o pid,ppid,ni,cmd
PID PPID NI CMD
16462 1630 10 /bin/bash ./jobcontrol.sh
$
$ ./jobcontrol.sh > jobcontrol.out &
[2] 16642
$
$ ps -p 16642 -o pid,ppid,ni,cmd
PID PPID NI CMD
16642 1630 0 /bin/bash ./jobcontrol.sh
$
$ renice -n 10 -p 16642
16642 (process ID) old priority 0, new priority 10
$
$ ps -p 16642 -o pid,ppid,ni,cmd
PID PPID NI CMD
16642 1630 10 /bin/bash ./jobcontrol.sh
$
at [-f filename] time
$ cat tryat.sh
#!/bin/bash
# Trying out the at command
#
echo "This script ran at $(date +%B%d,%T)"
echo
echo "This script is using the $SHELL shell."
echo
sleep 5
echo "This is the script's end."
#
exit
$
$ at -f tryat.sh now
warning: commands will be executed using /bin/sh
job 3 at Thu Jun 18 16:23:00 2020
$
$ cat tryatout.sh
#!/bin/bash
# Trying out the at command redirecting output
#
outfile=$HOME/scripts/tryat.out
#
echo "This script ran at $(date +%B%d,%T)" > $outfile
echo >> $outfile
echo "This script is using the $SHELL shell." >> $outfile
echo >> $outfile
sleep 5
echo "This is the script's end." >> $outfile
#
exit
$
$ at -M -f tryatout.sh now
warning: commands will be executed using /bin/sh
job 4 at Thu Jun 18 16:48:00 2020
$
$ cat $HOME/scripts/tryat.out
This script ran at June18,16:48:21
This script is using the /bin/bash shell.
This is the script's end.
$
$ at -M -f tryatout.sh teatime
warning: commands will be executed using /bin/sh
job 5 at Fri Jun 19 16:00:00 2020
$
$ at -M -f tryatout.sh tomorrow
warning: commands will be executed using /bin/sh
job 6 at Fri Jun 19 16:53:00 2020
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
5 Fri Jun 19 16:00:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
5 Fri Jun 19 16:00:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
$ atrm 5
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
minutepasthour hourofday dayofmonth month dayofweek command
15 10 * * * command
15 16 * * 1 command
00 12 1 * * command
00 12 28-31 * * if [ "$(date +%d -d tomorrow)" = 01 ] ; then command ; fi
这行脚本会在每天中午 12 点检查当天是不是当月的最后一天(28~31),如果是,就由 cron 执行 command。另一种方法是将 command 替换成一个控制脚本(controlling script),在可能是每月最后一 天的时候运行。控制脚本包含 if-then 语句,用于检查第二天是否为某个月的第一天。如果是,则由控制脚本发出命令,执行必须在当月最后一天执行的内容。
15 10 * * * /home/christine/backup.sh > backup.out
$ crontab -l
no crontab for christine
$
$ ls /etc/cron.*ly
/etc/cron.daily:
0anacron apt-compat cracklib-runtime logrotate [...]
apport bsdmainutils dpkg man-db [...]
/etc/cron.hourly:
/etc/cron.monthly:
0anacron
/etc/cron.weekly:
0anacron man-db update-notifier-common
$
$ ls /var/spool/anacron
cron.daily cron.monthly cron.weekly
$
$ sudo cat /var/spool/anacron/cron.daily
[sudo] password for christine:
20200619
$
period delay identifier command
source $scriptToRun > $scriptOutput & #Run script in background