20220820 10. 认识与学习BASH
10.1 认识 BASH 这个 Shell
管理整个计算机硬件的其实是操作系统的核心 (kernel),这个核心是需要被保护的! 所以我们一般使用者就只能通过 shell 来跟核心沟 通,以让核心达到我们所想要达到的工作
10.1.1 硬件、核心与 Shell
硬件、核心与用户的相关性
可以发现应用程序其实是在最外层,就如同鸡蛋的 外壳一样,因此这个咚咚也就被称呼为壳程序 (shell)
其实壳程序的功能只是提供使用者操作系统的一个接口,因此这个壳程序需要可以调用其他 软件才好。 我们在第四章到第九章提到过很多指令,包括 man, chmod, chown, vi, fdisk, mkfs 等等指令,这些指令都是独立的应用程序, 但是我们可以通过壳程序 (就是命令行界 面) 来操作这些应用程序,让这些应用程序调用核心来运行所需的工作哩!
只要能够操作应用程序的接口都能够称为壳程序。狭义的壳程序指的是命令 行方面的软件,包括本章要介绍的 bash 等。 广义的壳程序则包括图形接口的软件!因为图 形接口其实也能够操作各种应用程序来调用核心工作啊!
10.1.2 为何要学命令行的 shell?
不同的 distribution 所设计 的 X window 接口也都不相同
几乎各家 distributions 使用的 bash 都是一样的
10.1.3 系统的合法 shell 与 /etc/shells 功能
Linux 使用的这一种版本就称为“ Bourne Again SHell (简称 bash) ”,这个 Shell 是 Bourne Shell 的增强版本,也是基准于 GNU 的架构下发展出来的
shell 的简单历史:第一个流行的 shell 是由 Steven Bourne 发展出来的,为了纪念他所以就称为 Bourne shell ,或直接简称为 sh !而后 来另一个广为流传的 shell 是由柏克莱大学的 Bill Joy 设计依附于 BSD 版的 Unix 系统中的 shell ,这个 shell 的语法有点类似 C 语言,所以才得名为 C shell ,简称为 csh !由于在学术 界 Sun 主机势力相当的庞大,而 Sun 主要是 BSD 的分支之一,所以 C shell 也是另一个很重 要而且流传很广的 shell 之一 。
我们的 Linux (以 CentOS 7.x 为例) 有多少我们可以使用的 shells 呢? 你可以检 查一下 /etc/shells 这个文件,至少就有下面这几个可以用的 shells
-
/bin/sh (已经被 /bin/bash 所取代)
-
/bin/bash (就是 Linux 默认的 shell)
-
/bin/tcsh (整合 C Shell ,提供更多的功能)
-
/bin/csh (已经被 /bin/tcsh 所取代)
某些 FTP 网站会去检查使用者的可用 shell ,而如果你不想要让这些使用者使用 FTP 以外的主机资源时,可能会给予该使用者一些怪怪的 shell,让使用者无法以其他服务登 陆主机。 这个时候,你就得将那些怪怪的 shell 写到 /etc/shells 当中了。举例来说,我们的 CentOS 7.x 的 /etc/shells 里头就有个 /sbin/nologin 文件的存在,这个就是我们说的怪怪的 shell 啰
使用者什么时候可以取得 shell 来工作呢?还有, 我这个使用者默认 会取得哪一个 shell 啊?
当我登陆的时候,系统就会给我一个 shell 让我来工作了。 而这个登陆取得的 shell 就记 录在 /etc/passwd 这个文件内,在每一行的最后一个数据,就是你登陆后可以取得的默认的 shell 啦
10.1.4 Bash shell 的功能
bash 是 GNU 计 划中重要的工具软件之一,目前也是 Linux distributions 的标准 shell 。 bash 主要相容于 sh ,并且依据一些使用者需求而加强的 shell 版本。不论你使用的是那个 distribution ,你都难 逃需要学习 bash 的宿命啦!
bash 主要的优点有下面几个:
-
命令编修能力 (history):
- ~/.bash_history 记录的是前一次登陆以前所执行过的指令, 而至于这一次登陆所执行的指令 都被暂存在内存中,当你成功的登出系统后,该指令记忆才会记录到 .bash_history 当中
-
命令与文件补全功能: ([tab] 按键的好处)
-
[Tab] 接在一串指令的第一个字的后面,则为命令补全;
-
[Tab] 接在一串指令的第二个字以后时,则为“文件补齐”!
-
若安装 bash-completion 软件,则在某些指令后面使用 [tab] 按键时,可以进行“选项/参数的补齐”功能!
-
-
命令别名设置功能: (alias)
-
工作控制、前景背景控制: (job control, foreground, background)
-
程序化脚本: (shell scripts)
-
万用字符: (Wildcard)
ls -l /usr/bin/X*
10.1.5 查询指令是否为 Bash shell 的内置命令: type
man bash
查看 bash 的说明文档
为 了方便 shell 的操作,其实 bash 已经“内置”了很多指令了
怎么知道这个指令是来自于外部指令(指的是其他非 bash 所提供的指令) 或是内置在 bash 当中的呢? 嘿嘿!利用 type 这个指令来观察即可
type [-tpa] name
选项与参数:
:不加任何选项与参数时,type 会显示出 name 是外部指令还是 bash 内置指令
-t :当加入 -t 参数时,type 会将 name 以下面这些字眼显示出他的意义:
file :表示为外部指令;
alias :表示该指令为命令别名所设置的名称;
builtin :表示该指令为 bash 内置的指令功能;
-p :如果后面接的 name 为外部指令时,才会显示完整文件名;
-a :会由 PATH 变量定义的路径中,将所有含 name 的指令都列出来,包含 alias
type ls
type -t ls
type -a ls
type cd # cd 是 shell 内置指令
由于利用 type 搜 寻后面的名称时,如果后面接的名称并不能以可执行文件的状态被找到, 那么该名称是不会 被显示出来的。也就是说, type 主要在找出“可执行文件”而不是一般文件文件名
10.1.6 指令的下达与快速编辑按钮
[Enter] 按键是紧接着反斜线 (\) 的,两者中间没有其他字符。 因为 \ 仅跳脱“紧接着的下一个字符”而已!
组合键 | 功能与示范 |
---|---|
[ctrl]+u [ctrl]+k |
从光标处向前删除指令串 ([ctrl]+u) 从光标处向后删除指令串 ([ctrl]+k) |
[ctrl]+a [ctrl]+e |
让光标移动到整个指令串的最前面 ([ctrl]+a) 让光标移动到整个指令串的最后面 ([ctrl]+e) |
10.2 Shell 的变量功能
10.2.1 什么是变量?
为了区 别与自订变量的不同,环境变量通常以大写字符来表示
变量就是以一组文字或符号等,来取 代一些设置或者是一串保留的数据
10.2.2 变量的取用与设置:echo, 变量设置规则, unset
变量的取用: echo
# 语法
echo $variable
# 示例
echo $PATH
如何“设置”或者是“修改” 某 个变量的内容啊?很简单啦!用“等号(=)”连接变量与他的内容就好
在 bash 当中,当一个变量名称尚未被设置时,默认的内容是“空”的。
变量的设置规则
-
变量与变量内容以一个等号
=
来链接 -
等号两边不能直接接空白字符
-
变量名称只能是英文字母与数字,但是开头字符不能是数字
-
变量内容若有空白字符可使用双引号
"
或单引号'
将变量内容结合起来-
双引号内的特殊字符如
$
等,可以保有原本的特性,如下所示:var="lang is $LANG"
则echo $var
可得lang is zh_TW.UTF-8
-
单引号内的特殊字符则仅为一般字符 (纯文本),如下所示:
var='lang is $LANG'
则echo $var
可得lang is $LANG
-
-
可用跳脱字符
\
将特殊符号(如 [Enter], $, , 空白字符, ' 等)变成一般字符 -
在一串指令的执行中,还需要借由其他额外的指令所提供的信息时,可以使用反单引号
指令
或 $(指令)- 反单引号 ` 是键盘上方的数字键 1 左边那个按键
-
若该变量为扩增变量内容时,则可用
$变量名称
或${变量}
累加内容,如下所示:PATH="$PATH":/home/bin
或PATH=${PATH}:/home/bin
-
若该变量需要在其他子程序执行,则需要以 export 来使变量变成环境变量:
export PATH
-
通常大写字符为系统默认变量,自行设置变量可以使用小写字符,方便判断
-
取消变量的方法为使用 unset :
unset 变量名称
# 范例一:设置一变量 name ,且内容为 VBird
name=VBird
# 范例二:承上题,若变量内容 VBird's name
name="VBird's name"
name=VBird\'s\ name
# 范例三:我要在 PATH 这个变量当中“累加”:/home/dmtsai/bin 这个目录
PATH=$PATH:/home/dmtsai/bin
PATH="$PATH":/home/dmtsai/bin
PATH=${PATH}:/home/dmtsai/bin
# 范例四:承范例三,我要将 name 的内容多出 "yes"
name="$name"yes
name=${name}yes # 推荐
# 范例五:让我刚刚设置的 name=VBird 可以用在下个 shell 的程序
echo $name
export name
bash # 进入子程序
echo $name
exit # 离开子程序
在我目前这个 shell 的情况下,去启用另一个新的 shell ,新的那 个 shell 就是子程序
export 不能在不同会话间生效
# 范例六:进入到目前核心的模块目录
cd /lib/modules/`uname -r`/kernel
cd /lib/modules/$(uname -r)/kernel # 推荐!
# 范例七:取消刚刚设置的 name 这个变量内容
unset name
10.2.3 环境变量的功能
用 env 观察环境变量与常见环境变量说明
env 是 environment (环境) 的简写
# 范例一:列出目前的 shell 环境下的所有环境变量与其内容。
env
HOSTNAME=study.centos.vbird # 这部主机的主机名称
TERM=xterm # 这个终端机使用的环境是什么类型
SHELL=/bin/bash # 目前这个环境下,使用的 Shell 是哪一个程序?
HISTSIZE=1000 # “记录指令的笔数”在 CentOS 默认可记录 1000 笔
OLDPWD=/home/dmtsai # 上一个工作目录的所在 LC_ALL=en_US.utf8 # 由于语系的关系,鸟哥偷偷丢上来的一个设置
USER=dmtsai # 使用者的名称啊!
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:
or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32: *.tar=01... # 一些颜色显示
MAIL=/var/spool/mail/dmtsai # 这个使用者所取用的 mailbox 位置
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
PWD=/home/dmtsai # 目前使用者所在的工作目录 (利用 pwd 取出!)
LANG=zh_TW.UTF-8 # 这个与语系有关,下面会再介绍!
HOME=/home/dmtsai # 这个使用者的主文件夹啊!
LOGNAME=dmtsai # 登陆者用来登陆的帐号名称
_=/usr/bin/env # 上一次使用的指令的最后一个参数(或指令本身)
RANDOM 这个玩意儿就是“随机乱数”的变量啦!目前大多数的 distributions 都会有乱数 产生器,那就是 /dev/random 这个文件。 我们可以通过这个乱数文件相关的变量 ($RANDOM
) 来随机取得乱数值喔。在 BASH 的环境下,这个 RANDOM 变量的内 容,介于 0~32767 之间,所以,你只要 echo $RANDOM
时,系统就会主动的随机取出 一个介于 0~32767 的数值
# 生成 0-32767 之间的随机值
echo $RANDOM
# 生成 0-9 之间的随机值
declare -i number=$RANDOM*10/32768 ; echo $number
用 set 观察所有变量 (含环境变量与自订变量)
bash 可不只有环境变量喔,还有一些与 bash 操作接口有关的变量,以及使用者自己定义的 变量存在的。
set 除了环 境变量之外, 还会将其他在 bash 内的变量通通显示出来哩!
set
BASH=/bin/bash # bash 的主程序放置路径
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.2.46(1)-release' # 这两行是 bash 的版本啊!
COLUMNS=90 # 在目前的终端机环境下,使用的字段有几个字符长度
HISTFILE=/home/dmtsai/.bash_history # 历史命令记录的放置文件,隐藏文件
HISTFILESIZE=1000 # 存起来(与上个变量有关)的文件之指令的最大纪录笔数。
HISTSIZE=1000 # 目前环境下,内存中记录的历史命令最大笔数。
IFS=$' \t\n' # 默认的分隔符号
LINES=20 # 目前的终端机下的最大行数
MACHTYPE=x86_64-redhat-linux-gnu # 安装的机器类型
OSTYPE=linux-gnu # 操作系统的类型!
PS1='[\u@\h \W]\$ ' # PS1 就厉害了。这个是命令提示字符,也就是我们常见的 [root@www ~]# 或 [dmtsai ~]$ 的设置值啦!可以更动的!
PS2='> ' # 如果你使用跳脱符号 (\) 第二行以后的提示字符也
$ # 目前这个 shell 所使用的 PID
? # 刚刚执行完指令的回传值。
测试时,没有 $
和 ?
PS1:(提示字符的设置)
- \d :可显示出“星期 月 日”的日期格式,如:"Mon Feb 2"
- \H :完整的主机名称。举例来说,鸟哥的练习机为“study.centos.vbird”
- \h :仅取主机名称在第一个小数点之前的名字,如鸟哥主机则为“study”后面省略
- \t :显示时间,为 24 小时格式的“HH:MM:SS”
- \T :显示时间,为 12 小时格式的“HH:MM:SS”
- \A :显示时间,为 24 小时格式的“HH:MM”
- @ :显示时间,为 12 小时格式的“am/pm”样式
- \u :目前使用者的帐号名称,如“dmtsai”;
- \v :BASH 的版本信息,如鸟哥的测试主机版本为 4.2.46(1)-release,仅取“4.2”显示
- \w :完整的工作目录名称,由根目录写起的目录名称。但主文件夹会以 ~ 取代;
- \W :利用 basename 函数取得工作目录名称,所以仅会列出最后一个目录名。
#
:下达的第几个指令。$
:提示字符,如果是 root 时,提示字符为#
,否则就是$
$:(关于本 shell 的 PID)
想要知道我们的 shell 的 PID ,就可以用: echo $$
即可!
?:(关于上个执行指令的回传值)
一般来说,如果成功的 执行该指令, 则会回传一个 0 值,如果执行过程发生错误,就会回传“错误代码”才对!一般 就是以非为 0 的数值来取代。
export: 自订变量转成环境变量
环境变量与自订变量,那么这两者之间有啥差异呢?其实 这两者的差异在于“ 该变量是否会被子程序所继续引用”
子程序仅会继承父程序的环境变量, 子 程序不会继承父程序的自订变量
export 变量名称
如果仅下达 export 而没有接变量时,那么此时将会把所有的“环境变 量”秀出来
10.2.4 影响显示结果的语系变量 (locale)
# 查看当前使用的编码
locale
# 查看Linux支持的编码
locale -a
基本上,你可以逐一设置每个与语系有关的变量数据,但事实上,如果其他的语系变量都未 设置, 且你有设置 LANG 或者是 LC_ALL 时,则其他的语系变量就会被这两个变量所取代!
系统默认的语系定义在哪里 呢? 其实就是在 /etc/locale.conf
# 设置系统编码
LANG=en_US.utf8
export LC_ALL=en_US.utf8
10.2.5 变量的有效范围
如果在跑程序的时候,有父程序与子程序的不同程序关系时, 则“变量”可否被引用与 export 有关。被 export 后的变量,我们可以称他为“环境变量”! 环境变量可以被子程序所引 用,但是其他的自订变量内容就不会存在于子程序中。
“全域变量, global variable”与“区域变量, local variable”
环境变量=全域变量
自订变量=区域变量
10.2.6 变量键盘读取、阵列与宣告: read, array, declare
read
read [-pt] variable
选项与参数:
-p :后面可以接提示字符!
-t :后面可以接等待的“秒数!”
# 范例一:让使用者由键盘输入一内容,将该内容变成名为 atest 的变量
read atest
This is a test # 此时光标会等待你输入!请输入左侧文字看看
echo ${atest}
This is a test # 你刚刚输入的数据已经变成一个变量内容!
# 范例二:提示使用者 30 秒内输入自己的大名,将该输入字串作为名为 named 的变量内容
read -p "Please keyin your name: " -t 30 named
Please keyin your name: VBird Tsai # 注意看,会有提示字符喔!
echo ${named}
VBird Tsai # 输入的数据又变成一个变量的内容了!
declare / typeset
declare 或 typeset 是一样的功能,就是在“宣告变量的类型”
declare [-aixr] variable
选项与参数:
-a :将后面名为 variable 的变量定义成为阵列 (array) 类型
-i :将后面名为 variable 的变量定义成为整数数字 (integer) 类型
-x :用法与 export 一样,就是将后面的 variable 变成环境变量;
-r :将变量设置成为 readonly 类型,该变量不可被更改内容,也不能 unset
# 范例一:让变量 sum 进行 100+300+50 的加总结果
sum=100+300+50
echo ${sum}
100+300+50
declare -i sum=100+300+50
echo ${sum}
450
在默认的情况下面, bash 对于变量有几个基本的定义:
-
变量类型默认为“字串”,所以若不指定变量类型,则 1+2 为一个“字串”而不是“计算式”。所以上述第一个执行的结果才会出现那个情况的;
-
bash 环境中的数值运算,默认最多仅能到达整数形态,所以 1/3 结果是 0;
# 范例二:将 sum 变成环境变量
declare -x sum
export | grep sum
# 范例三:让 sum 变成只读属性,不可更动!
declare -r sum
# 范例四:让 sum 变成非环境变量的自订变量吧!
declare +x sum
declare -p sum # -p 可以单独列出变量的类型
阵列 (array) 变量类型
# 范例:设置上面提到的 var[1] ~ var[3] 的变量。
var[1]="small min"
var[2]="big min"
var[3]="nice min"
echo "${var[1]}, ${var[2]}, ${var[3]}"
10.2.7 与文件系统及程序的限制关系: ulimit
bash 是可以“限制使用者的某些系统资源”的
ulimit [-SHacdfltu] [配额]
选项与参数:
-H :hard limit ,严格的设置,必定不能超过这个设置的数值;
-S :soft limit ,警告的设置,可以超过这个设置值,但是若超过则有警告讯息。 在设置上,通常 soft 会比 hard 小,举例来说,soft 可设置为 80 而 hard 设置为 100,那么你可以使用到 90 (因为没有超过 100),但介于 80~100 之间时, 统会有警告讯息通知你!
-a :后面不接任何选项与参数,可列出所有的限制额度;
-c :当某些程序发生错误时,系统可能会将该程序在内存中的信息写成文件(除错用), 这种文件就被称为核心文件(core file)。此为限制每个核心文件的最大容量。
-f :此 shell 可以创建的最大文件大小(一般可能设置为 2GB)单位为 KBytes
-d :程序可使用的最大断裂内存(segment)容量;
-l :可用于锁定 (lock) 的内存量
-t :可使用的最大 CPU 时间 (单位为秒)
-u :单一使用者可以使用的最大程序(process)数量。
# 范例一:列出你目前身份(假设为一般帐号)的所有限制数据数值
ulimit -a
# 范例二:限制使用者仅能创建 10MBytes 以下的容量的文件
ulimit -f 10240
ulimit -a | grep 'file size'
想要复原 ulimit 的设置最简单的方法就是登出再登陆,否则就是得要重新以 ulimit 设置 才行! 不过,要注意的是,一般身份使用者如果以 ulimit 设置了 -f 的文件大小, 那么他“只 能继续减小文件大小,不能增加文件大小喔!”
10.2.8 变量内容的删除、取代与替换 (Optional)
变量内容的删除与取代
变量设置方式 | 说明 |
---|---|
$ | 若变量内容从头开始的数据符合“关键字”,则将符合的最短数据 删除 |
$ | 若变量内容从头开始的数据符合“关键字”,则将符合的最 长数据删除 |
$ | 若变量内容从尾向前的数据符合“关键字”,则将符合的最短数据 删除 |
$ | 若变量内容从尾向前的数据符合“关键字”,则将符合的最 长数据删除 |
$ | 若变量内容符合“旧字串”则“第一个旧字串会被新字串取代” |
$ | 若变 量内容符合“旧字串”则“全部的旧字串会被新字串取代” |
变量的测试与内容替换
变量设置方式 | str 没有设置 | str 为空字串 | str 已设置非为空字串 |
---|---|---|---|
var=\({str-expr} | var=expr | var= | var=\)str | |||
var=\({str:-expr} | var=expr | var=expr | var=\)str | |||
var=$ | var= | var=expr | var=expr |
var=$ | var= | var= | var=expr |
var=\({str=expr} | str=expr var=expr | str 不变 var= | str 不变 var=\)str | |||
var=\({str:=expr} | str=expr var=expr | str=expr var=expr | str 不变 var=\)str | |||
var=\({str?expr} | expr 输出至 stderr | var= | var=\)str | |||
var=\({str:?expr} | expr 输出至 stderr | expr 输出至 stderr | var=\)str |
10.3 命令别名与历史命令
10.3.1 命令别名设置: alias, unalias
# 示例:创建别名
alias lm='ls -al | more'
alias rm='rm -i'
# 查看所有别名
alias
# 取消命令别名
unalias lm
10.3.2 历史命令:history
history [n]
history [-c]
history [-raw] histfiles
选项与参数:
n :数字,意思是“要列出最近的 n 笔命令列表”的意思!
-c :将目前的 shell 中的所有 history 内容全部消除
-a :将目前新增的 history 指令新增入 histfiles 中,若没有加 histfiles , 则默认写入 ~/.bash_history
-r :将 histfiles 的内容读到目前这个 shell 的 history 记忆中;
-w :将目前的 history 记忆内容写入 histfiles 中!
在正常的情况下,历史命令的读取与记录是这样的:
-
当我们以 bash 登陆 Linux 主机之后,系统会主动的由主文件夹的 ~/.bash_history 读取 以前曾经下过的指令,那么 ~/.bash_history 会记录几笔数据呢?这就与你 bash 的 HISTFILESIZE 这个变量设置值有关了!
-
假设我这次登陆主机后,共下达过 100 次指令,“等我登出时, 系统就会将 101~1100 这 总共 1000 笔历史命令更新到 ~/.bash_history 当中。” 也就是说,历史命令在我登出时, 会将最近的 HISTFILESIZE 笔记录到我的纪录档当中啦!
-
当然,也可以用 history -w 强制立刻写入的!那为何用“更新”两个字呢? 因为 ~/.bash_history 记录的笔数永远都是 HISTFILESIZE 那么多,旧的讯息会被主动的拿 掉! 仅保留最新的!
!number
!command
!!
选项与参数:
number :执行第几笔指令的意思;
command :由最近的指令向前搜寻“指令串开头为 command”的那个指令,并执行;
!! :就是执行上一个指令(相当于按↑按键后,按 Enter)
同一帐号同时多次登陆的 history 写入问题
同时开好几个 bash 接口,这些 bash 的身份都是 root 。 所有的 bash 都有自己的 1000 笔记录在内存中。因为等到登出时才会更新记录文件,所 以啰, 最后登出的那个 bash 才会是最后写入的数据
无法记录时间
10.4 Bash Shell 的操作环境
10.4.1 路径与指令搜寻顺序
-
以相对/绝对路径执行指令,例如“ /bin/ls ”或“ ./ls ”;
-
由 alias 找到该指令来执行;
-
由 bash 内置的 (builtin) 指令来执行;
-
通过 $PATH 这个变量的顺序搜寻到的第一个指令来执行。
10.4.2 bash 的进站与欢迎讯息: /etc/issue, /etc/motd
在终端机接口 (tty1 ~ tty6) 登陆的时候,会有几行提示的字串
在 /etc/issue
里面设置
issue 这个文件的内容也是可以使用反斜线作为变量取用
代码 | 描述 |
---|---|
\d | 本地端时间的日期; |
\l | 显示第几个终端机接口; |
\m | 显示硬件的等级 (i386/i486/i586/i686...); |
\n | 显示主机的网络名称; |
\O | 显示 domain name; |
\r | 操作系统的版本 (相当于 uname -r) |
\t | 显示本地端时间的时间; |
\S | 操作系统的名称; |
\v | 操作系统的版本。 |
至于如果您想要让使用者登陆后取得一些讯息,可以 将讯息加入 /etc/motd
里面去
10.4.3 bash 的环境配置文件
前几个小节谈到的命令别名啦、自订的变量啦,在你 登出 bash 后就会失效,所以你想要保留你的设置, 就得要将这些设置写入配置文件才行
login 与 non-login shell
login shell 与 non-login shell! 重 点在于有没有登陆 (login) 啦
-
login shell:取得 bash 时需要完整的登陆流程的,就称为 login shell。举例来说,你要由 tty1 ~ tty6 登陆,需要输入使用者的帐号与密码,此时取得的 bash 就称为“ login shell ”
-
non-login shell:取得 bash 接口的方法不需要重复登陆的举动,举例
-
你以 X window 登陆 Linux 后, 再以 X 的图形化接口启动终端机,此时那个终端接口并没有需 要再次的输入帐号与密码,那个 bash 的环境就称为 non-login shell了。
-
你在原本 的 bash 环境下再次下达 bash 这个指令,同样的也没有输入帐号密码, 那第二个 bash (子程序) 也是 non-login shell 。
-
这两个取得 bash 的情况中,读取的配置文 件数据并不一样
一般来说,login shell 其实只会读取这两个配置文件:
-
/etc/profile
:这是系统整体的设置,你最好不要修改这个文件; -
~/.bash_profile
或~/.bash_login
或~/.profile
:属于使用者个人设置,你要改自己的数据,就写入这里
/etc/profile (login shell 才会读)
bash 的 login shell 情况下所读取的整体环境配置文件其实只有 /etc/profile,但是 /etc/profile 还会调用出其他的配置文件,所以让我们的 bash 操作接口变的 非常的友善
这个配置文件可以利用使用者的识别码 (UID) 来决定很多重要的变量数据, 这也是每个使用者登陆取得 bash 时一定会读取的配 置文件
如果你想要帮所有使用者设置整体环境,那就是改这里
这个文件设置的变量主要有:
-
PATH:会依据 UID 决定 PATH 变量要不要含有 sbin 的系统指令目录;
-
MAIL:依据帐号设置好使用者的 mailbox 到 /var/spool/mail/帐号名;
-
USER:根据使用者的帐号设置此一变量内容;
-
HOSTNAME:依据主机的 hostname 指令决定此一变量内容;
-
HISTSIZE:历史命令记录笔数。CentOS 7.x 设置为 1000 ;
-
umask:包括 root 默认为 022 而一般用户为 002 等!
/etc/profile
还会去调用外部的设置数据
/etc/profile.d/*.sh
只要在 /etc/profile.d/
这个目录内且扩展名为 .sh ,另外,使 用者能够具有 r 的权限, 那么该文件就会被 /etc/profile
调用进来
这个 目录下面的文件规范了 bash 操作接口的颜色、 语系、ll 与 ls 指令的命令别名、vi 的命令别 名、which 的命令别名等等。
如果你需要帮所有使用者设置一些共享的命令别名时, 可以在 这个目录下面自行创建扩展名为 .sh 的文件,并将所需要的数据写入即可
/etc/locale.conf
这个文件是由 /etc/profile.d/lang.sh 调用进来的!这也是我们决定 bash 默认使用何种语系的 重要配置文件! 文件里最重要的就是 LANG/LC_ALL 这些个变量的设置
/usr/share/bash-completion/completions/*
除了命令补齐、文件名补齐之外,还可以进行指令的选项/ 参数补齐功能!那就是从这个目录里面找到相对应的指令来处理的! 其实这个目录下面的内 容是由 /etc/profile.d/bash_completion.sh
这个文件载入的啦!
~/.bash_profile (login shell 才会读)
在 login shell 的 bash 环境中,所读取的个人偏好配置文件其实主要有三个,依序分别是:
-
~/.bash_profile
-
~/.bash_login
-
~/.profile
其实 bash 的 login shell 设置只会读取上面三个文件的其中一个, 而读取的顺序则是依照上 面的顺序。也就是说,如果 ~/.bash_profile 存在,那么其他两个文件不论有无存在,都不会 被读取。
.bash_profile 的内容是:
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH
这个文件内有设置 PATH 这个变量喔!而且还使用了 export 将 PATH 变成环境变量呢! 由于 PATH 在 /etc/profile 当中已经设置过,所以在这里就以累加的方式增加使用者主文件夹下的 ~/bin/ 为额外的可执行文件放置目录。这也就是说,你可以将自己创建的可执行文件放置到你 自己主文件夹下的 ~/bin/ 目录啦!
判断主文件夹下的 ~/.bashrc 存在否,若存在则读入 ~/.bashrc 的设置
login shell 的配置文件读取流程
实线的的方向是主线流程,虚线的方向则是被调用的配置文件
在 CentOS 的 login shell 环境下,最终被读取的配置文件是“ ~/.bashrc ”这个文件
source :读入环境配置文件的指令
由于 /etc/profile 与 ~/.bash_profile 都是在取得 login shell 的时候才会读取的配置文件,所 以, 如果你将自己的偏好设置写入上述的文件后,通常都是得登出再登陆后,该设置才会生 效。
那么,能不能直接读取配置文件而不登出登陆呢? 可以的!那就得要利用 source 这个指 令了!
source 配置文件文件名
# 范例:将主文件夹的 ~/.bashrc 的设置读入目前的 bash 环境中
source ~/.bashrc
. ~/.bashrc
~/.bashrc (non-login shell 会读)
当你取得 non-login shell 时,该 bash 配置文件仅会读取 ~/.bashrc 而已
默认的 ~/.bashrc 内容是:
# .bashrc
# User specific aliases and functions
#alias rm='rm -i'
#alias cp='cp -i'
#alias mv='mv -i'
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
CentOS 7.x 还会主动的调用 /etc/bashrc 这个文件喔!/etc/bashrc 帮我们的 bash 定义出下面的数据:
-
依据不同的 UID 规范出 umask 的值;
-
依据不同的 UID 规范出提示字符 (就是 PS1 变量);
-
调用
/etc/profile.d/*.sh
的设置
/etc/bashrc 是 CentOS 特有的 (其实是 Red Hat 系统特有的),其他 不同的 distributions 可能会放置在不同的文件名就是了。
由于这个 ~/.bashrc 会调用 /etc/bashrc 及 /etc/profile.d/*.sh , 所以,万一你没有 ~/.bashrc (可能自己不小心将他删除 了),那么你会发现你的 bash 提示字符可能会变成这个样子:
-bash-4.2$
因为你并没有调用 /etc/bashrc 来规范 PS1 变量啦
如果你想要将命令提示字符捉回来,那么可以复制 /etc/skel/.bashrc 到你的主文件夹,再修订一下你所想要的内容, 并使用 source 去调用 ~/.bashrc ,那你的命令提示字符就会回来啦!
其他相关配置文件
/etc/man_db.conf
这个文件规定了下达 man 指令的时候,该去哪里查看数据的路径设置
~/.bash_history
历史命令就记录在这里
这个文件能够记录几笔数据,则与 HISTFILESIZE 这个变量有关
~/.bash_logout
这个文件则记录了“当我登出 bash 后,系统再帮我做完什么动作后才离开”的意思。
10.4.4 终端机的环境设置: stty, set
可以在 tty1 ~ tty6 这六个命令行的终端机 (terminal) 环境中登陆,登陆的时候我们可以取得一些字符设置的功能
登陆终端机的时候,会自动的取得一些终端机的输入环境的设置
stty [-a]
选项与参数:
-a :将目前所有的 stty 参数列出来;
除了 stty 之外,其实我们的 bash 还有自己的一些终端机设置值呢!那就是利用 set 来设置 的
set [-uvCHhmBx]
选项与参数:
-u :默认不启用。若启用后,当使用未设置变量时,会显示错误讯息;
-v :默认不启用。若启用后,在讯息被输出前,会先显示讯息的原始内容;
-x :默认不启用。若启用后,在指令被执行前,会显示指令内容(前面有 ++ 符号)
-h :默认启用。与历史命令有关;
-H :默认启用。与历史命令有关;
-m :默认启用。与工作管理有关;
-B :默认启用。与刮号 [] 的作用有关;
-C :默认不启用。若使用 > 等,则若文件存在时,该文件不会被覆盖。
# 范例一:显示目前所有的 set 设置值
echo $-
bash 默认的组合键
组合按键 | 执行结果 |
---|---|
Ctrl + C | 终止目前的命令 |
Ctrl + D | 输入结束 (EOF),例如邮件结束的时候; |
Ctrl + M | 就是 Enter 啦! |
Ctrl + S | 暂停屏幕的输出 |
Ctrl + Q | 恢复屏幕的输出 |
Ctrl + U | 在提示字符下,将整列命令删除 |
Ctrl + Z | “暂停”目前的命令 |
10.4.5 万用字符与特殊符号
符号 | 意义 |
---|---|
* | 代表“ 0 个到无穷多个”任意字符 |
? | 代表“一定有一个”任意字符 |
[ ] | 同样代表“一定有一个在括号内”的字符(非任意字符)。例如 [abcd] 代表“一定有一 个字符, 可能是 a, b, c, d 这四个任何一个” |
[-] | 若有减号在中括号内时,代表“在编码顺序内的所有字符”。例如 [0-9] 代表 0 到 9 之 间的所有数字,因为数字的语系编码是连续的! |
[^] | 若中括号内的第一个字符为指数符号 (^) ,那表示“反向选择”,例如 [^abc] 代表 一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。 |
# 范例一:找出 /etc/ 下面以 cron 为开头的文件名
ll -d /etc/cron*
# 范例二:找出 /etc/ 下面文件名“刚好是五个字母”的文件名
ll -d /etc/?????
# 范例三:找出 /etc/ 下面文件名含有数字的文件名
ll -d /etc/*[0-9]*
# 范例四:找出 /etc/ 下面,文件名开头非为小写字母的文件名:
ll -d /etc/[^a-z]*
# 范例五:将范例四找到的文件复制到 /tmp/upper 中
mkdir /tmp/upper; cp -a /etc/[^a-z]* /tmp/upper
bash 环境中的特殊符号有哪些
符号 | 内容 |
---|---|
# | 注解符号:这个最常被使用在 script 当中,视为说明!在后的数据均不执行 |
\ | 跳脱符号:将“特殊字符或万用字符”还原成一般字符 |
| |
管线 (pipe):分隔两个管线命令的界定(后两节介绍); |
; | 连续指令下达分隔符号:连续性命令的界定 (注意!与管线命令并不相同) |
~ | 使用者的主文件夹 |
$ | 取用变量前置字符:亦即是变量之前需要加的变量取代值 |
& | 工作控制 (job control):将指令变成背景下工作 |
! | 逻辑运算意义上的“非” not 的意思! |
/ | 目录符号:路径分隔的符号 |
>, >> | 数据流重导向:输出导向,分别是“取代”与“累加” |
<, << | 数据流重导向:输入导向 (这两个留待下节介绍) |
' ' | 单引号,不具有变量置换的功能 ($ 变为纯文本) |
" " | 具有变量置换的功能! ($ 可保留相关功能) |
` | 两个“ ` ”中间为可以先执行的指令,亦可使用 $() |
() | 在中间为子 shell 的起始与结束 |
在中间为命令区块的组合! |
文件名尽量不要使用到上述的字符
10.5 数据流重导向
数据流重导向就是将某个指令执行后应该要出现在屏幕上的数据, 给他传输到 其他的地方
10.5.1 什么是数据流重导向
指令执行 过程的数据传输情况
standard output 与 standard error output 分别代表“标准输出 (STDOUT)”与“标准错误输出 (STDERR)”, 这两个玩意儿默认都是输出到屏幕上面来的
standard output 与 standard error output
标准输出指的是“指令执行所回传的正确的讯息”,而标准错误输出可理解为“ 指令 执行失败后,所回传的错误讯息”
举个简单例子来说,我们的系统默认有 /etc/crontab 但却无 /etc/vbirdsay, 此时若下达“ cat /etc/crontab /etc/vbirdsay ”这个指令时,cat 会进行:
-
标准输出:读取 /etc/crontab 后,将该文件内容显示到屏幕上;
-
标准错误输出:因为无法找到 /etc/vbirdsay,因此在屏幕上显示错误讯息
数据流重导向可以 将 standard output (简称 stdout) 与 standard error output (简称 stderr) 分别传送到其他 的文件或设备去,而分别传送所用的特殊字符则如下所示:
-
标准输入 (stdin) :代码为 0 ,使用 < 或 << ;
-
标准输出 (stdout):代码为 1 ,使用 > 或 >> ;
-
标准错误输出(stderr):代码为 2 ,使用 2> 或 2>> ;
覆盖 与累加
-
1> :以覆盖的方法将“正确的数据”输出到指定的文件或设备上;
-
1>>:以累加的方法将“正确的数据”输出到指定的文件或设备上;
-
2> :以覆盖的方法将“错误的数据”输出到指定的文件或设备上;
-
2>>:以累加的方法将“错误的数据”输出到指定的文件或设备上;
“ 1>> ”以及“ 2>> ”中间是没有空格的
# 范例一:观察你的系统根目录 (/) 下各目录的文件名、权限与属性,并记录下来
ll / > ~/rootfile
# 范例二:利用一般身份帐号搜寻 /home 下面是否有名为 .bashrc 的文件存在
find /home -name .bashrc
# 范例三:承范例二,将 stdout 与 stderr 分存到不同的文件去
find /home -name .bashrc > list_right 2> list_error
/dev/null 垃圾桶黑洞设备与特殊写法
黑洞设备 /dev/null 可以吃掉任何导向这个设备的信息
# 范例四:承范例三,将错误的数据丢弃,屏幕上显示正确的数据
find /home -name .bashrc 2> /dev/null
# 范例五:将指令的数据全部写入名为 list 的文件中
find /home -name .bashrc > list 2> list # 错误
find /home -name .bashrc > list 2>&1 # 正确
find /home -name .bashrc &> list # 正确
范例五第一行错误的原因是,由于两股数据同时写入一个文件,又没有使用特殊的语法, 此时两股数据可能会交叉写入该文件内,造成次序的错乱。所以虽然最终 list 文件还是会产 生,但是里面的数据排列就会怪怪的,而不是原本屏幕上的输出排序。
写入同一个文件 的特殊语法如上表所示,你可以使用 2>&1
也可以使用 &>
standard input : < 与 <<
<
表示将原本需要由键盘输入的数据,改由文件内容来取代
# 范例六:利用 cat 指令来创建一个文件的简单流程
cat > catfile
testing
cat file test
# 这里按下 [ctrl]+d 来离开
# 查看文件内容
cat catfile
# 范例七:用 stdin 取代键盘的输入以创建新文件的简单流程
cat > catfile < ~/.bashrc
ll catfile ~/.bashrc
<<
代表的是“结束的输入字符”
# 用 cat 直接将输入的讯息输出到 catfile 中, 且当由键盘输入 eof 时,该次输入就结 束
cat > catfile << "eof"
利用 << 右侧的控制字符,我们可以终止一次输入, 而不必输入 [crtl]+d 来结束
为何要使用命令输出重导向呢?
-
屏幕输出的信息很重要,而且我们需要将他存下来的时候;
-
背景执行中的程序,不希望他干扰屏幕正常的输出结果时;
-
一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的执行结果,希望他可以存下来时;
-
一些执行命令的可能已知错误讯息时,想以“ 2> /dev/null ”将他丢掉时;
-
错误讯息与正确讯息需要分别输出时。
# 将 echo "error message" 以 standard error output 的格式来输出
echo "error message" 1>&2
echo "error message" 2> /dev/null 1>&2
10.5.2 命令执行的判断依据: ;
, &&
, ||
很多指令我想要一次输入去执行,而不想要分次执行
cmd ; cmd (不考虑指令相关性的连续指令下达)
# 在关机的时候我希望可以先执行两次 sync 同步化写入磁盘后才 shutdown 计算机
sync; sync; shutdown -h now
$? (指令回传值) 与 && 或 ||
两个指令之间有相依性,而这个相依性主要判断的地方就在于前一个指令 执行的结果是否正确。若前一个指令执行的结果为正确,在 Linux 下面会 回传一个 $? = 0 的值
指令下达情况 | 说明 |
---|---|
cmd1 && cmd2 | 1. 若 cmd1 执行完毕且正确执行($?=0 ),则开始执行 cmd2。 2. 若 cmd1 执行完毕且为错误 ( $?≠0 ),则 cmd2 不执行。 |
cmd1 | | cmd2 |
# 范例一:使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 创建 /tmp/abc/hehe
ls /tmp/abc && touch /tmp/abc/hehe # 没有abc文件夹,ls执行报错,不执行touch
mkdir /tmp/abc
ls /tmp/abc && touch /tmp/abc/hehe # 存在abc文件夹,ls执行成功,执行touch
# 范例二:测试 /tmp/abc 是否存在,若不存在则予以创建,若存在就不作任何事情
rm -r /tmp/abc # 先删除此目录以方便测试
ls /tmp/abc || mkdir /tmp/abc
ll -d /tmp/abc
# 范例三:不论 /tmp/abc 是否存在,就是要创建 /tmp/abc/hehe 文件
ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe
&& 与 || 的顺序很重要
# 以 ls 测试 /tmp/vbirding 是否存在,若存在则显示 "exist" ,若不存在,则显示 "not exist"!
ls /tmp/vbirding && echo "exist" || echo "not exist"
ls /tmp/vbirding || echo "not exist" && echo "exist"
# 若存在则显示 "exist"
# 若不存在则显示 "not exist" 和 "exist"
一般来说,假设判断式有三个,也就是:
command1 && command2 || command3
而且顺序通常不会变,因为一般来说, command2 与 command3 会放置肯定可以执行成功 的指令
10.6 管线命令 (pipe)
bash 命令执行的时候有输出的数据会出现! 那么如果这群数据必需要 经过几道手续之后才能得到我们所想要的格式,应该如何来设置?
管线命令 (pipe)使用的是“ | ”这个界定符号
管线命令与“连续下达命令”是不 一样的
# 示例:查看 /etc 下的文件
ls -al /etc | less
管线命令主要有两个比较需要注意的地方:
-
管线命令仅会处理 standard output,对于 standard error output 会予以忽略
-
管线命令必须要能够接受来自前一个指令的数据成为 standard input 继续处理才行。
如果你硬要让 standard error 可以被管线命令所使用,那该如何处理?其实就 是通过上一小节的数据流重导向即可! 让 2>&1
加入指令中就可以让 2> 变成 1>
10.6.1 撷取命令: cut, grep
撷取命令就是将一段数据经过分析后,取出我们所想要的。或者是经由 分析关键字,取得我们所想要的那一行!
不过,要注意的是,一般来说,撷取讯息通常是针 对“一行一行”来分析的, 并不是整篇讯息分析的喔
cut
这个指令可以将一段讯息的某一段给他“切”出来~ 处理的讯息是 以“行”为单位
cut -d'分隔字符' -f fields # 用于有特定分隔字符
cut -c 字符区间 # 用于排列整齐的讯息
选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;
# 范例一:将 PATH 变量取出,我要找出第五个路径。
echo ${PATH}
echo ${PATH} | cut -d ':' -f 5 # 第五个
echo ${PATH} | cut -d ':' -f 3,5 # 第三个和第五个
echo ${PATH} | cut -d ':' -f 3-5 # 第三个到第五个
# 范例二:将 export 输出的讯息,取得第 12 字符以后的所有字串
export
export | cut -c 12- # 不想要“ declare -x ”时
# 范例三:用 last 将显示的登陆者的信息中,仅留下使用者大名
last
last | cut -d ' ' -f 1
grep
cut 是将一行讯息当中,取出某部分我们想要的,而 grep 则是分析一行讯息, 若当中 有我们所需要的信息,就将该行拿出来
grep [-acinv] [--color=auto] '搜寻字串' filename
选项与参数:
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字串' 内容的那一行!
--color=auto :可以将找到的关键字部分加上颜色的显示喔!
# 范例一:将 last 当中,有出现 root 的那一行就取出来;
last | grep 'root'
# 范例二:与范例一相反,只要没有 root 的就取出!
last | grep -v 'root'
# 范例三:在 last 的输出讯息中,只要有 root 就取出,并且仅取第一栏
last | grep 'root' |cut -d ' ' -f1
# 范例四:取出 /etc/man_db.conf 内含 MANPATH 的那几行
grep --color=auto 'MANPATH' /etc/man_db.conf
# 神奇的是,如果加上 --color=auto 的选项,找到的关键字部分会用特殊颜色显示喔!
grep 可以解析一行文字,取得关键字,若该行有存在关键字,就会整行列出来!另外, CentOS 7 当中,默认的 grep 已经主动加上 --color=auto 在 alias 内了
10.6.2 排序命令: sort, wc, uniq
sort
排序的字符与语系的编码有关
sort [-fbMnrtuk] [file or stdin]
选项与参数:
-f :忽略大小写的差异,例如 A 与 a 视为编码相同;
-b :忽略最前面的空白字符部分;
-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
-n :使用“纯数字”进行排序(默认是以文字体态来排序的);
-r :反向排序;
-u :就是 uniq ,相同的数据中,仅出现一行代表;
-t :分隔符号,默认是用 [tab] 键来分隔;
-k :以那个区间 (field) 来进行排序的意思
# 范例一:个人帐号都记录在 /etc/passwd 下,请将帐号进行排序。
cat /etc/passwd | sort
# 范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三栏来排序,该如何?
cat /etc/passwd | sort -t ':' -k 3
# 范例三:利用 last ,将输出的数据仅取帐号,并加以排序
last | cut -d ' ' -f1 | sort
uniq
这个指令用来将“重复的行删除掉只显示一个”
uniq [-ic]
选项与参数:
-i :忽略大小写字符的不同;
-c :进行计数
# 范例一:使用 last 将帐号列出,仅取出帐号栏,进行排序后仅取出一位;
last | cut -d ' ' -f1 | sort | uniq
# 范例二:承上题,如果我还想要知道每个人的登陆总次数呢?
last | cut -d ' ' -f1 | sort | uniq -c
wc
wc [-lwm]
选项与参数:
-l :仅列出行;
-w :仅列出多少字(英文单字);
-m :多少字符;
# 范例一:那个 /etc/man_db.conf 里面到底有多少相关字、行、字符数?
cat /etc/man_db.conf | wc
131 723 5171
# 输出的三个数字中,分别代表: “行、字数、字符数”
# 范例二:我知道使用 last 可以输出登陆者,但是 last 最后两行并非帐号内容,那么请问, 我该如何以一行指令串取得登陆系统的总人次?
last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | grep -v 'unknown' |wc -l
# 帐号文件中有多少个帐号
cat /etc/passwd | wc -l
10.6.3 双向重导向: tee
tee 会同时将数据流分送到文件去与屏幕 (screen);而输出到屏幕的,其实就是 stdout
tee [-a] file
选项与参数:
-a :以累加 (append) 的方式,将数据加入 file 当中!
last | tee last.list | cut -d " " -f1 # 这个范例可以让我们将 last 的输出存一份到 last.list 文件中;
ls -l /home | tee ~/homefile | more # 这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出讯息!
ls -l / | tee -a ~/homefile | more # 要注意! tee 后接的文件会被覆盖,若加上 -a 这个选项则能将讯息累加。
10.6.4 字符转换命令: tr, col, join, paste, expand
tr
tr 可以用来删除一段讯息当中的文字,或者是进行文字讯息的替换!
由正则表达式的方式来取代数据
tr [-ds] SET1 ...
选项与参数:
-d :删除讯息当中的 SET1 这个字串;
-s :取代掉重复的字符!
# 范例一:将 last 输出的讯息中,所有的小写变成大写字符:
last | tr '[a-z]' '[A-Z]'
# 事实上,没有加上单引号也是可以执行的,如:“ last | tr [a-z] [A-Z] ”
# 范例二:将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
cat /etc/passwd | tr -d ':'
# 范例三:将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除
cp /etc/passwd ~/passwd && unix2dos ~/passwd
cat ~/passwd | tr -d '\r' > ~/passwd.linux
# 那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr
ll /etc/passwd ~/passwd*
# 处理过后,发现文件大小与原本的 /etc/passwd 就一致了!
col
用来简单的处理将 [tab] 按键取代成为空 白键
col [-xb]
选项与参数:
-x :将 tab 键转换成对等的空白键
# 范例一:利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空白
cat /etc/man_db.conf | col -x | cat -A | more
join
处理两个文件之间的数据, 而且, 主要是在处理两个文件当中,有 "相同数据" 的那一行,才将他加在一起
join [-ti12] file1 file2
选项与参数:
-t :join 默认以空白字符分隔数据,并且比对“第一个字段”的数据, 如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个!
-i :忽略大小写的差异;
-1 :这个是数字的 1 ,代表“第一个文件要用那个字段来分析”的意思;
-2 :代表“第二个文件要用那个字段来分析”的意思。
# 范例一:用 root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一栏
head -n 3 /etc/passwd /etc/shadow
# 由输出的数据可以发现这两个文件的最左边字段都是相同帐号!且以 : 分隔
join -t ':' /etc/passwd /etc/shadow | head -n 3
# 通过上面这个动作,我们可以将两个文件第一字段相同者整合成一列!
# 第二个文件的相同字段并不会显示(因为已经在最左边的字段出现了啊!)
# 范例二:我们知道 /etc/passwd 第四个字段是 GID ,那个 GID 记录在 /etc/group 当中的第三个字段,请问如何将两个文件整合?
head -n 3 /etc/passwd /etc/group
join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
需要特别注意的是,在使用 join 之前,你所需要处理的文件应该要事先经过排序 (sort) 处理! 否则有些比对的项目会被略过
paste
paste 直接“将两行贴在一起,且中间以 [tab] 键隔开”
paste [-d] file1 file2
选项与参数:
-d :后面可以接分隔字符。默认是以 [tab] 来分隔的!
- :如果 file 部分写成 - ,表示来自 standard input 的数据的意思。
# 范例一:用 root 身份,将 /etc/passwd 与 /etc/shadow 同一行贴在一起
paste /etc/passwd /etc/shadow
# 范例二:先将 /etc/group 读出(用 cat),然后与范例一贴上一起!且仅取出前三行
cat /etc/group | paste /etc/passwd /etc/shadow - | head -n 3
# 重点在那个 - 的使用
expand
将 [tab] 按键转成空白键
expand [-t] file
选项与参数:
-t :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空白键取代。
我们也可以自行定义一个 [tab] 按键代表多少个字符呢!
# 范例一:将 /etc/man_db.conf 内行首为 MANPATH 的字样就取出;仅取前三行;
grep '^MANPATH' /etc/man_db.conf | head -n 3
# 范例二:承上,如果我想要将所有的符号都列出来?(用 cat)
grep '^MANPATH' /etc/man_db.conf | head -n 3 |cat -A
# 范例三:承上,我将 [tab] 按键设置成 6 个字符的话?
grep '^MANPATH' /etc/man_db.conf | head -n 3 | expand -t 6 - | cat -A
也可以参考一下 unexpand 这个将空白转成 [tab] 的指令功能
10.6.5 分区命令: split
可以帮 你将一个大文件,依据文件大小或行数来分区,就可以将大文件分区成为小文件了
split [-bl] file PREFIX
选项与参数:
-b :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等;
-l :以行数来进行分区。
PREFIX :代表前置字符的意思,可作为分区文件的前导文字。
# 范例一:我的 /etc/services 有六百多K,若想要分成 300K 一个文件时?
cd /tmp; split -b 300k /etc/services services
ll -k services*
# 那个文件名可以随意取的啦!我们只要写上前导文字,小文件就会以
# xxxaa, xxxab, xxxac 等方式来创建小文件的!
# 范例二:如何将上面的三个小文件合成一个文件,文件名为 servicesback
cat services* >> servicesback
# 范例三:使用 ls -al / 输出的信息中,每十行记录成一个文件
ls -al / | split -l 10 - lsroot
一般来说,如果需要 stdout/stdin 时,但偏偏又没有文件, 有的只是 -
时,那么那个 -
就会被当成 stdin 或 stdout
10.6.6 参数代换: xargs
xargs 可以读入 stdin 的数据,并且以空白字符或断行字符作为分辨,将 stdin 的数据分隔成 为 arguments 。
因为是以空白字符作为分隔,所以,如果有一些文件名或者是其他意义的名 词内含有空白字符的时候, xargs 可能就会误判了
xargs [-0epn] command
选项与参数:
-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空白键等等字符时,这个 -0 参数 可以将他还原成一般字符。这个参数可以用于特殊状态喔!
-e :这个是 EOF (end of file) 的意思。后面可以接一个字串,当 xargs 分析到这个字串时, 就会停止继续工作!
-p :在执行每个指令的 argument 时,都会询问使用者的意思;
-n :后面接次数,每次 command 指令执行时,要使用几个参数的意思。
当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!
# 范例一:将 /etc/passwd 内的第一栏取出,仅取三行,使用 id 这个指令将每个帐号内容秀出来
id root
# id 指令可以查询使用者的 UID/GID 等信息
id $(cut -d ':' -f 1 /etc/passwd | head -n 3)
# 虽然使用 $(cmd) 可以预先取得参数,但可惜的是, id 这个指令“仅”能接受一个参数而已!
# 所以上述的这个指令执行会出现错误!
cut -d ':' -f 1 /etc/passwd | head -n 3 | id
uid=1000(dmtsai) gid=1000(dmtsai) groups=1000(dmtsai),10(wheel)
# 因为 id 并不是管线命令,因此在上面这个指令执行后,前面的东西通通不见!只会执行 id
cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs id
# 依旧会出现错误!这是因为 xargs 一口气将全部的数据通通丢给 id 处理~但 id 就接受 1 个啊最多!
cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id
# 通过 -n 来处理,一次给予一个参数,因此上述的结果就 OK 正常的显示
# 范例二:同上,但是每次执行 id 时,都要询问使用者是否动作?
cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id
# 范例三:将所有的 /etc/passwd 内的帐号都以 id 查阅,但查到 sync 就结束指令串
cut -d ':' -f 1 /etc/passwd | xargs -e'sync' -n 1 id
# -e'sync' 是连在一起的,中间没有空白键
# 范例四:找出 /usr/sbin 下面具有特殊权限的文件名,并使用 ls -l 列出详细属性
find /usr/sbin -perm /7000 | xargs ls -l
# 等价于 ls -l $(find /usr/sbin -perm /7000)
10.6.7 关于减号 - 的用途
在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的 stdin , 某些指令需要用到文件名称 (例如 tar) 来进行处理时,该 stdin 与 stdout 可以利用减号 "-" 来替代
tar -cvf - /home | tar -xvf - -C /tmp/homeback
将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是 传送到 stdout; 经过管线后,将 tar -cvf - /home 传送给后面的 tar -xvf -
后面的这个 - 则 是取用前一个指令的 stdout, 因此,我们就不需要使用 filename 了