Linux笔记:2-认识与学习BASH

@

认识与学习BASH

操作系统其实是一组软件, 由于这组软件在控制整个硬件与管理系统的活动监测。
壳程序的功能只是提供使用者操作系统的一个接口, 因此这个壳程序需要可以调用其他软件才行。
之前学习的指令都是独立的应用程序, 但是我们可以通过壳程序 ( 就是命令行界面) 来操作这些应用程序, 让这些应用程序调用核心来运行所需的工作。
 
只要能够操作应用程序的接口都能够称为壳程序。
狭义的壳程序指的是命令行方面的软件, 包括 bash 等。
广义的壳程序则包括图形接口的软件! 因为图形接口其实也能够操作各种应用程序来调用核心工作。

关于Bash

系统的合法 shell 与 /etc/shells 功能

目前我们的 Linux ( 以 CentOS 7.x 为例);
你可以检查一下 /etc/shells 这个文件, 至少就有下面这几个可以用的 shells

  • /bin/sh ( 已经被 /bin/bash 所取代)
  • /bin/bash ( 就是 Linux 默认的 shell)
  • /bin/tcsh ( 整合 C Shell , 提供更多的功能)
  • /bin/csh ( 已经被 /bin/tcsh 所取代)

系统上合法的 shell 需要写入 /etc/shells 这个文件;
因为系统某些服务在运行过程中, 会去检查使用者能够使用的 shells , 而这些shell 的查询就是借由 /etc/shells 这个文件。
 
当用户登陆的时候, 系统就会给我一个 shell 让用户来工作;
而这个登陆取得的 shell 就记录在 /etc/passwd 这个文件内。

[dmtsai@study ~]$ cat /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
.....( 下面省略) .....

Bash shell 的功能

  • 命令记忆能力 ( history) :
    他能记忆使用过的指令;保存在用户的主文件夹内的 .bash_history中;
    ~/.bash_history 记录的是前一次登陆以前所执行过的指令, 而至于这一次登陆所执行的指令都被暂存在内存中, 当你成功的登出系统后, 该指令记忆才会记录到 .bash_history 当中 。
  • 命令与文件补全功能: ( [tab] 按键的好处)
  • 命令别名设置功能: ( alias)
  • 工作控制、 前景背景控制: ( job control, foreground, background)
  • 程序化脚本: ( shell scripts)
    Linux 下面的 shellscripts, 可以将你平时管理系统常需要下达的连续指令写成一个文件, 该文件并且可以通过对谈互动式的方式来进行主机的侦测工作。
  • 模糊字符匹配:
    例如:想要知道 /usr/bin 下面有多少以 X 为开头的文件? 使用: “ ls -l /usr/bin/X* ”。

查询指令是否为 Bash shell 的内置命令: type

为了方便 shell 的操作, 其实 bash 已经“内置”了很多指令;
为了区分指令是来自于外部指令( 指的是其他非 bash 所提供的指令) 或是内置在bash ;利用 type 这个指令来观察即可

[dmtsai@study ~]$ type [-tpa] name
选项与参数:
		: 不加任何选项与参数时, type 会显示出 name 是外部指令还是 bash 内置指令
-t 		: 当加入 -t 参数时, type 会将 name 以下面这些字眼显示出他的意义:
file 	: 表示为外部指令;
alias 	: 表示该指令为命令别名所设置的名称;
builtin : 表示该指令为 bash 内置的指令功能;
-p 		: 如果后面接的 name 为外部指令时, 才会显示完整文件名;
-a 		: 会由 PATH 变量定义的路径中, 将所有含 name 的指令都列出来, 包含 alias

范例一: 查询一下 ls 这个指令是否为 bash 内置?
[dmtsai@study ~]$ type ls
ls is aliased to `ls --color=auto' <==未加任何参数, 列出 ls 的最主要使用情况
[dmtsai@study ~]$ type -t ls
alias                              <==仅列出 ls 执行时的依据
[dmtsai@study ~]$ type -a ls
ls is aliased to `ls --color=auto' <==最先使用 aliase
ls is /usr/bin/ls                  <==还有找到外部指令在 /bin/ls

范例二: 那么 cd 呢?
[dmtsai@study ~]$ type cd
cd is a shell builtin              <==看到了吗? cd 是 shell 内置指令

通过 type 这个指令我们可以知道每个指令是否为 bash 的内置指令
此外, 由于利用 type 搜寻后面的名称时, 如果后面接的名称并不能以可执行文件的状态被找到, 那么该名称是不会被显示出来的。
也就是说, type 主要在找出“可执行文件”而不是一般文件文件名
所以, 这个 type 也可以用来作为类似 which 指令的用途

指令的下达与快速编辑按钮

当指令过长时,可以使用 \ + [Enter] 来换行输入;
 
另外, 当你所需要下达的指令特别长, 或者是你输入了一串错误的指令时, 你想要快速的将这串指令整个删除掉, 一般来说, 我们都是按下删除键的来删除;也可以使用下面的快捷键:

组合键 功能与示范
[ctrl]+u/[ctrl]+k 分别是从光标处向前删除指令串 ( [ctrl]+u) 及向后删除指令串( [ctrl]+k) 。
[ctrl]+a/[ctrl]+e 分别是让光标移动到整个指令串的最前面 ( [ctrl]+a) 或最后面( [ctrl]+e) 。

Shell 的变量功能

Linux 是多用户多任务的环境, 每个人登陆系统都能取得一个 bash shell;
不同用户的shell有自己的shell环境,不同环境是通过变量来设置。

在 Linux System 下面, 所有的线程都是需要一个执行码;
你“真正以 shell 来跟 Linux 沟通, 是在正确的登陆 Linux 之后! ”这个时候你就有一个 bash 的执行程序, 也才可以真正的经由 bash 来跟系统沟通;
而在进入 shell之前,由于系统需要一些变量来提供shell数据的存取 ( 或者是一些环境的设置参数值, 例如是否要显示彩色等等的) , 所以就有一些所谓的“环境变量” 需要来读入系统中了!
这些环境变量例如 PATH、 HOME、 MAIL、 SHELL 等等, 都是很重要的;
为了区别与自订变量的不同, 环境变量通常以大写字符来表示。

变量的取用与设置: echo, 变量设置规则, unset

你可以利用 echo 这个指令来取用变量, 但是, 变量在被取用时,前面必须要加上钱字号“ $ ”才行。

[dmtsai@study ~]$ echo $variable
[dmtsai@study ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
[dmtsai@study ~]$ echo ${PATH}  # 近年来, 鸟哥比较偏向使用这种格式喔!

在 bash 当中, 当一个变量名称尚未被设置时, 默认的内容是“空”的
另外, 变量在设置时, 还是需要符合某些规定的, 否则会设置失败:

  1. 变量与变量内容以一个等号“=”来链接, 如下所示:
    “myname=VBird”
  2. 等号两边不能直接接空白字符, 如下所示为错误:
    “myname = VBird”或“myname=VBirdTsai”
  3. 变量名称只能是英文字母与数字, 但是开头字符不能是数字, 如下为错误:
    “2myname=VBird”
  4. 变量内容若有空白字符可使用双引号“"”或单引号“'”将变量内容结合起来, 但
  • 双引号内的特殊字符如 $ 等, 可以保有原本的特性, 如下所示:
    “var="lang is$LANG"”则“echo $var”可得“lang is zh_TW.UTF-8”
  • 单引号内的特殊字符则仅为一般字符 ( 纯文本) ,
    如下所示: “var='lang is$LANG'”则“echo $var”可得“lang is $LANG”
  1. 可用跳脱字符“ \ ”将特殊符号( 如 [Enter], $, , 空白字符, '等) 变成一般字符, 如:
    “myname=VBird\ Tsai”
  2. 在一串指令的执行中, 还需要借由其他额外的指令所提供的信息时, 可以使用反单引号“ ` ”或 “$( 指令)”。 特别注意, 那个 ` 是键盘上方的数字键 1 左边那个按键, 而不是单引号!
    例如想要取得核心版本的设置: “version=$( uname -r) ”再“echo$version”可得“3.10.0-229.el7.x86_64”
  3. 若该变量为扩增变量内容时, 则可用 "$变量名称" 或 ${变量} 累加内容, 如下所示
    “PATH="$PATH":/home/bin”或“PATH=${PATH}:/home/bin”
  4. 若该变量需要在其他子程序执行, 则需要以 export 来使变量变成环境变量: “export PATH”
  5. 通常大写字符为系统默认变量, 自行设置变量可以使用小写字符, 方便判断 ( 纯粹依照使用者兴趣与嗜好);
  6. 取消变量的方法为使用 unset : “unset 变量名称”例如取消 myname 的设置: “unset myname”
范例一: 设置一变量 name , 且内容为 VBird
[dmtsai@study ~]$ 12name=VBird
bash: 12name=VBird: command not found... <==屏幕会显示错误! 因为不能以数字开头!
[dmtsai@study ~]$ name = VBird <==还是错误! 因为有空白!
[dmtsai@study ~]$ name=VBird <==OK 的啦!
范例二: 承上题, 若变量内容为 VBird's name 呢, 就是变量内容含有特殊符号时:
[dmtsai@study ~]$ name=VBird's name
# 单引号与双引号必须要成对, 在上面的设置中仅有一个单引号, 因此当你按下 enter 后,
# 你还可以继续输入变量内容。 这与我们所需要的功能不同, 失败啦!
# 记得, 失败后要复原请按下 [ctrl]-c 结束!
[dmtsai@study ~]$ name="VBird's name" <==OK 的啦!
# 指令是由左边向右找→, 先遇到的引号先有用, 因此如上所示, 单引号变成一般字符!
[dmtsai@study ~]$ name='VBird's name' <==失败的啦!
# 因为前两个单引号已成对, 后面就多了一个不成对的单引号了! 因此也就失败了!
[dmtsai@study ~]$ name=VBird\'s\ name <==OK 的啦!
# 利用反斜线 ( \) 跳脱特殊字符, 例如单引号与空白键, 这也是 OK 的啦!
范例三: 我要在 PATH 这个变量当中“累加”:/home/dmtsai/bin 这个目录
[dmtsai@study ~]$ PATH=$PATH:/home/dmtsai/bin
[dmtsai@study ~]$ PATH="$PATH":/home/dmtsai/bin
[dmtsai@study ~]$ PATH=${PATH}:/home/dmtsai/bin
# 上面这三种格式在 PATH 里头的设置都是 OK 的! 但是下面的例子就不见得啰!
范例四: 承范例三, 我要将 name 的内容多出 "yes" 呢?
[dmtsai@study ~]$ name=$nameyes
# 知道了吧? 如果没有双引号, 那么变量成了啥? name 的内容是 $nameyes 这个变量!
# 呵呵! 我们可没有设置过 nameyes 这个变量呐! 所以, 应该是下面这样才对!
[dmtsai@study ~]$ name="$name"yes
[dmtsai@study ~]$ name=${name}yes <==以此例较佳!
范例五: 如何让我刚刚设置的 name=VBird 可以用在下个 shell 的程序?
[dmtsai@study ~]$ name=VBird
[dmtsai@study ~]$ bash <==进入到所谓的子程序
[dmtsai@study ~]$ echo $name <==子程序: 再次的 echo 一下;
<==嘿嘿! 并没有刚刚设置的内容喔!
[dmtsai@study ~]$ exit <==子程序: 离开这个子程序
[dmtsai@study ~]$ export name
[dmtsai@study ~]$ bash <==进入到所谓的子程序
[dmtsai@study ~]$ echo $name <==子程序: 在此执行!
VBird <==看吧! 出现设置值了!
[dmtsai@study ~]$ exit <==子程序: 离开这个子程序

关于子程序:
在一般的状态下, 父程序的自订变量是无法在子程序内使用的。
但是通过 export 将变量变成环境变量后, 就能够在子程序下面应用。

范例六: 如何进入到您目前核心的模块目录?
[dmtsai@study ~]$ cd /lib/modules/`uname -r`/kernel
[dmtsai@study ~]$ cd /lib/modules/$( uname -r) /kernel # 以此例较佳!

每个 Linux 都能够拥有多个核心版本, 且几乎 distribution 的核心版本都不相同。 以 CentOS7.1 ( 未更新前) 为例, 他的默认核心版本是 3.10.0-229.el7.x86_64 , 所以核心模块目录在/lib/modules/3.10.0-229.el7.x86_64/kernel/ 内。 也由于每个 distributions 的这个值都不相同, 但是我们却可以利用 uname -r 这个指令先取得版本信息。
 
其实上面的指令可以说是作了两次动作, 亦即是:

  1. 先进行反单引号内的动作“uname -r”并得到核心版本为 3.10.0-229.el7.x86_64
  2. 将上述的结果带入原指令, 故得指令为: “cd /lib/modules/3.10.0-229.el7.x86_64/kernel/”

注意:
在一串指令中, 在反单引号之内的指令和$(指定)将会被先执行, 而其执行出来的结果将做为外部的输入信息。

环境变量的功能

环境变量的作用有:包括主文件夹的变换、 提示字符的显示、 可执行文件搜寻的路径等等。

用 env 观察环境变量 与 常见环境变量说明

范例一: 列出目前的 shell 环境下的所有环境变量与其内容。
[dmtsai@study ~]$ 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               <== 这个使用者所取用的 mailbox 位置
LANG=zh_TW.UTF-8               <== 这个与语系有关, 下面会再介绍!
HOME=/home/dmtsai              <== 这个使用者的主文件夹啊!
LOGNAME=dmtsai                 <== 登陆者用来登陆的帐号名称
_=/usr/bin/env                 <== 上一次使用的指令的最后一个参数( 或指令本身)
  • HOME 代表使用者的主文件夹。
    还记得我们可以使用 cd ~ 去到自己的主文件夹吗? 或者利用 cd 就可以直接回到使用者主文件夹了。 那就是取用这个变量啦~ 有很多程序都可能会取用到这个变量的值!
  • SHELL 告知我们, 目前这个环境使用的 SHELL 是哪支程序? Linux 默认使用 /bin/bash的啦!
  • HISTSIZE 这个与“历史命令”有关, 亦即是, 我们曾经下达过的指令可以被系统记录下来, 而记录的“笔数”则是由这个值来设置的。
  • MAIL 当我们使用 mail 这个指令在收信时, 系统会去读取的邮件信箱文件 ( mailbox) 。
  • PATH 就是可执行文件搜寻的路径啦~目录与目录中间以冒号( :) 分隔, 由于文件的搜寻是依序由 PATH 的变量内的目录来查询, 所以, 目录的顺序也是重要的喔。
  • LANG 这个重要! 就是语系数据啰~很多讯息都会用到他, 举例来说, 当我们在启动某些 perl 的程序语言文件时, 他会主动的去分析语系数据文件, 如果发现有他无法解析的编码语系, 可能会产生错误喔! 一般来说, 我们中文编码通常是 zh_TW.Big5 或者是zh_TW.UTF-8, 这两个编码偏偏不容易被解译出来, 所以, 有的时候, 可能需要修订一下语系数据。
  • RANDOM 这个玩意儿就是“随机乱数”的变量啦! 目前大多数的 distributions 都会有乱数产生器, 那就是 /dev/random 这个文件。 我们可以通过这个乱数文件相关的变量( $RANDOM) 来随机取得乱数值喔。 在 BASH 的环境下, 这个 RANDOM 变量的内容, 介于 0~32767 之间, 所以, 你只要 echo $RANDOM 时, 系统就会主动的随机取出一个介于 0~32767 的数值。

用 set 观察所有变量 ( 含环境变量与自订变量)

bash 可不只有环境变量还有一些与 bash 操作接口有关的变量, 以及使用者自己定义的变量存在的。
set 除了环境变量之外, 还会将其他在 bash 内的变量通通显示出来

[dmtsai@study ~]$ 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
?                                     <== 刚刚执行完指令的回传值。
...
# 有许多可以使用的函数库功能被鸟哥取消啰! 请自行查阅!

一般来说, 不论是否为环境变量, 只要跟我们目前这个 shell 的操作接口有关的变量, 通常都会被设置为大写字符, 也就是说, “基本上, 在 Linux 默认的情况中, 使用{大写的字母}来设置的变量一般为系统内定需要的变量”。

变量大概说明:

  • PS1: ( 提示字符的设置)
    这是 PS1 ( 数字的 1 不是英文字母) , 这个东西就是我们的“命令提示字符”喔! 当我们每次按下 [Enter] 按键去执行某个指令后, 最后要再次出现提示字符时, 就会主动去读取这个变量值了。 上头 PS1 内显示的是一些特殊符号, 这些特殊符号可以显示不同的信息, 每个distributions 的 bash 默认的 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 ( Process ID) 。 想要知道我们的 shell的 PID , 就可以用: “ echo $$ ”即可! 出现的数字就是你的 PID 号码。
  • ?: ( 关于上个执行指令的回传值)
    这个变量是: “上一个执行的指令所回传的值”, 当我们执行某些指令时, 这些指令都会回传一个执行后的代码。
    一般来说, 如果成功的执行该指令, 则会回传一个 0 值, 如果执行过程发生错误, 就会回传“错误代码”才对。
  • OSTYPE, HOSTTYPE, MACHTYPE: ( 主机硬件与核心的等级)

export: 自订变量转成环境变量

env 与 set 现在知道有所谓的环境变量与自订变量, 那么这两者之间有啥差异呢?
其实这两者的差异在于“ 该变量是否会被子程序所继续引用”。

当你登陆 Linux 并取得一个 bash 之后, 你的 bash 就是一个独立的程序, 这个程序的识别使用的是一个称为程序识别码, 被称为 PID 的就是。 接下来你在这个 bash 下面所下达的任何指令都是由这个 bash 所衍生出来的, 那些被下达的指令就被称为子程序了。 我们可以用下面的图示来简单的说明一下父程序与子程序的概念:
在这里插入图片描述
子程序仅会继承父程序的环境变量, 子程序不会继承父程序的自订变量啦! 所以你在原本 bash 的自订变量在进入了子程序后就会消失不见, 一直到你离开子程序并回到原本的父程序后, 这个变量才会又出现。

export 指令就是将自订变量变成环境变量,可以让该变量值继续存在于子程序。

[dmtsai@study ~]$ export 变量名称

这东西用在“分享自己的变量设置给后来调用的文件或其他程序”。
像鸟哥常常在自己的主文件后面调用其他附属文件( 类似函数的功能) , 但是主文件与附属文件内都有相同的变量名称, 若一再重复设置时, 要修改也很麻烦, 此时只要在原本的第一个文件内设置好“ export变量 ”, 后面所调用的文件就能够使用这个变量设置了! 而不需要重复设置, 这非常实用于shell script 当中。
如果仅下达 export 而没有接变量时, 那么此时将会把所有的“环境变量”秀出来。

[dmtsai@study ~]$ export
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
declare -x LANG="zh_TW.UTF-8"
declare -x LC_ALL="en_US.utf8"

影响显示结果的语系变量 ( locale)

当我们使用 man command 的方式去查询某个数据的说明文档时, 该说明文档的内容可能会因为我们使用的语系不同而产生乱码。 另外, 利用 ls 查询文件的时间时, 也可能会有乱码出现在时间的部分。 那个问题其实就是语系
的问题。
可以由 locale 这个指令来查询 Linux 到底支持了多少的语系。

[dmtsai@study ~]$ locale -a
....( 前面省略) ....
zh_TW
zh_TW.big5 <==大五码的中文编码
zh_TW.euctw
zh_TW.utf8 <==万国码的中文编码
zu_ZA
zu_ZA.iso88591
zu_ZA.utf8

中文语系至少支持了两种以上的编码, 一种是目前还是很常见的 big5 , 另一种则是越来越热门的 utf-8 编码。
修订这些编码,可以通过下面这些变量:


[dmtsai@study ~]$ locale  	 <==后面不加任何选项与参数即可!
LANG=en_US                   <==主语言的环境
LC_CTYPE="en_US"             <==字符( 文字) 辨识的编码
LC_NUMERIC="en_US"           <==数字系统的显示讯息
LC_TIME="en_US"              <==时间系统的显示数据
LC_COLLATE="en_US"           <==字串的比较与排序等
LC_MONETARY="en_US"          <==币值格式的显示等
LC_MESSAGES="en_US"          <==讯息显示的内容, 如功能表、 错误讯息等
LC_ALL=                      <==整体语系的环境
....(後面省略)....

基本上, 你可以逐一设置每个与语系有关的变量数据, 但事实上, 如果其他的语系变量都未设置, 且你有设置 LANG 或者是 LC_ALL 时, 则其他的语系变量就会被这两个变量所取代

为什么在 Linux 主机的终端机接口 ( tty1 ~ tty6) 的环境下, 如果设置“ LANG=zh_TW.utf8 ”这个设置值生效后, 使用 man或者其他讯息输出时, 都会有一堆乱码, 尤其是使用 ls -l 这个参数时?
 
因为在 Linux 主机的终端机接口环境下是无法显示像中文这么复杂的编码文字, 所以就会产生乱码了。
也就是如此, 我们才会必须要在 tty1 ~ tty6 的环境下, 加装一些中文化接口的软件, 才能够看到中文。
不过, 如果你是在 MS Windows 主机以远端连线服务器的软件连线到主机的话, 那么, 其实命令行确实是可以看到中文的。 此时反而你得要在 LC_ALL设置中文编码。

整体系统默认的语系定义是在 /etc/locale.conf

[dmtsai@study ~]$ cat /etc/locale.conf
LANG=zh_TW.utf8
LC_NUMERIC=zh_TW.UTF-8
LC_TIME=zh_TW.UTF-8
LC_MONETARY=zh_TW.UTF-8
LC_PAPER=zh_TW.UTF-8
LC_MEASUREMENT=zh_TW.UTF-8

变量的有效范围

为什么环境变量的数据可以被子程序所引用呢? 这是因为内存配置的关系:

  • 当启动一个 shell, 操作系统会分配一记忆区块给 shell 使用, 此内存内之变量可让子程序取用
  • 若在父程序利用 export 功能, 可以让自订变量的内容写到上述的记忆区块当中( 环境变量) ;
  • 当载入另一个 shell 时 ( 亦即启动子程序, 而离开原本的父程序了) , 子 shell 可以将父shell 的环境变量所在的记忆区块导入自己的环境变量区块当中。

变量键盘读取、 数组与定义: read, array, declare

上面提到的变量设置功能, 都是由命令行直接设置的。
变量设置可以让使用者能够经由键盘输入。及通过交互的方式设置变量。

read

要读取来自键盘输入的变量, 就是用 read 这个指令了。 这个指令最常被用在 shell script 的撰写当中

[dmtsai@study ~]$ read [-pt] variable
选项与参数:
-p : 后面可以接提示字符!
-t : 后面可以接等待的“秒数! ”这个比较有趣~不会一直等待使用者啦!

范例一: 让使用者由键盘输入一内容, 将该内容变成名为 atest 的变量
[dmtsai@study ~]$ read atest
This is a test          <==此时光标会等待你输入! 请输入左侧文字看看
[dmtsai@study ~]$ echo ${atest}
This is a test          <==你刚刚输入的数据已经变成一个变量内容!

范例二: 提示使用者 30 秒内输入自己的大名, 将该输入字串作为名为 named 的变量内容
[dmtsai@study ~]$ read -p "Please keyin your name: " -t 30 named
Please keyin your name: VBird Tsai   <==注意看, 会有提示字符喔!
[dmtsai@study ~]$ echo ${named}
VBird Tsai        <==输入的数据又变成一个变量的内容了!

declare / typeset

declare 或 typeset 是一样的功能, 就是在“定义变量的类型”。
如果使用 declare 后面并没有接任何参数, 那么 bash 就会主动的将所有的变量名称与内容通通叫出来, 就好像使用 set 一样。

[dmtsai@study ~]$ declare [-aixr] variable
选项与参数:
-a : 将后面名为 variable 的变量定义成为阵列 ( array) 类型
-i : 将后面名为 variable 的变量定义成为整数数字 ( integer) 类型
-x : 用法与 export 一样, 就是将后面的 variable 变成环境变量;
-r : 将变量设置成为 readonly 类型, 该变量不可被更改内容, 也不能 unset

范例一: 让变量 sum 进行 100+300+50 的加总结果
[dmtsai@study ~]$ sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
100+300+50  <==咦! 怎么没有帮我计算加总? 因为这是文字体态的变量属性啊!
[dmtsai@study ~]$ declare -i sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
450         <==知道了吗??

由于在默认的情况下面, bash 对于变量有几个基本的定义:

  • 变量类型默认为“字串”, 所以若不指定变量类型, 则 1+2 为一个“字串”而不是“计算式”。所以上述第一个执行的结果才会出现那个情况的;
  • bash 环境中的数值运算, 默认最多仅能到达整数形态, 所以 1/3 结果是 0;

数组( array) 变量类型

在 bash 里头, 数组的设置方式是:

范例: 设置 var[1] ~ var[3] 的变量。
[dmtsai@study ~]$ var[1]="small min"
[dmtsai@study ~]$ var[2]="big min"
[dmtsai@study ~]$ var[3]="nice min"
[dmtsai@study ~]$ echo "${var[1]}, ${var[2]}, ${var[3]}"
small min, big min, nice min

与文件系统及程序的限制关系: ulimit

bash 是可以“限制使用者的某些系统资源”的, 包括可以打开的文件数量, 可以使用的 CPU 时间, 可以使用的内存总量等等。

[dmtsai@study ~]$ 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) 数量。

范例一: 列出你目前身份( 假设为一般帐号) 的所有限制数据数值
[dmtsai@study ~]$ ulimit -a
core file size          (blocks, -c) 0          <==只要是 0 就代表没限制
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited  <==可创建的单一文件的大小
pending signals                 (-i) 4903
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024       <==同时可打开的文件数量
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

范例二: 限制使用者仅能创建 10MBytes 以下的容量的文件
[dmtsai@study ~]$ ulimit -f 10240
[dmtsai@study ~]$ ulimit -a | grep 'file size'
core file size          (blocks, -c) 0
file size               (blocks, -f) 10240 <==最大量为10240Kbyes, 相当10MBytes

[dmtsai@study ~]$ dd if=/dev/zero of=123 bs=1M count=20
File size limit exceeded (core dumped) <==尝试创建 20MB 的文件, 结果失败了!

[dmtsai@study ~]$ rm 123  <==赶快将这个文件删除啰! 同时你得要登出再次的登陆才能解开 10M 的限制

单一 filesystem 能够支持的单一文件大小与 block 的大小有关。 但是文件系统的限制容量都允许的太大了! 如果想要让使用者创建的文件不要太大时, 我们是可以考虑用 ulimit 来限制使用者可以创建的文件大小!
利用ulimit -f 就可以来设置了! 例如上面的范例二, 要注意单位! 单位是 KBytes。
若改天你一直无法创建一个大容量的文件, 记得瞧一瞧 ulimit 的信息

想要复原 ulimit 的设置最简单的方法就是登出再登陆, 否则就是得要重新以 ulimit 设置才行!
不过, 要注意的是, 一般身份使用者如果以 ulimit 设置了 -f 的文件大小, 那么他“只能继续减小文件大小, 不能增加文件大小。
另外, 若想要管控使用者的 ulimit 限值pam

变量内容的删除、 取代与替换 ( Optional)

变量除了可以直接设置来修改原本的内容之外,还可以通过简单的动作来将变量的内容进行微调。
如:进行变量内容的删除、 取代与替换等。

变量内容的删除与取代

从前面开始删除变量内容
范例一: 先让小写的 path 自订变量设置的与 PATH 内容相同
[dmtsai@study ~]$ path=${PATH}
[dmtsai@study ~]$ echo ${path}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
范例二: 假设我不喜欢 local/bin, 所以要将前 1 个目录删除掉, 如何显示?
[dmtsai@study ~]$ echo ${path#/*local/bin:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

# : 代表“从变量内容的最前面开始向右删除”, 且仅删除最短的那个。
/*local/bin: 代表要被删除的部分, 由于 # 代表由前面开始删除, 所以这里便由开始的 / 写起。需要注意的是, 这里通过万用字符 * 来取代 0 到无穷多个任意字符。

范例三: 我想要删除前面所有的目录, 仅保留最后一个目录
[dmtsai@study ~]$ echo ${path#/*:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 由于一个 # 仅删除掉最短的那个, 因此他删除的情况可以用下面的删除线来看:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
[dmtsai@study ~]$ echo ${path##/*:}
/home/dmtsai/bin
# 嘿! 多加了一个 # 变成 ## 之后, 他变成“删除掉最长的那个数据”! 亦即是:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

# :匹配文字的『最短的』那一個;
##:匹配文字的【最长的】那一个

从后面向前删除变量内容
范例四: 我想要删除最后面那个目录, 亦即从 : 到 bin 为止的字串
[dmtsai@study ~]$ echo ${path%:*bin}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin
# 注意啊! 最后面一个目录不见去!
# 这个 % 符号代表由最后面开始向前删除! 所以上面得到的结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
范例五: 那如果我只想要保留第一个目录呢?
[dmtsai@study ~]$ echo ${path%%:*bin}
/usr/local/bin
# 同样的, %% 代表的则是最长的符合字串, 所以结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
取代
范例六: 将 path 的变量内容内的 sbin 取代成大写 SBIN:
[dmtsai@study ~]$ echo ${path/sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 这个部分就容易理解的多了! 关键字在于那两个斜线, 两斜线中间的是旧字串
# 后面的是新字串, 所以结果就会出现如上述的特殊字体部分啰!
[dmtsai@study ~]$ echo ${path//sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 如果是两条斜线, 那么就变成所有符合的内容都会被取代喔!

总结说明:

变量设置方式说明
${变量#关键字}
${变量##关键字}
若变量内容从头开始的数据符合“关键字”, 则将符合的最短数据删除
若变量内容从头开始的数据符合“关键字”, 则将符合的最长数据删除
${变量%关键字}
${变量%%关键字}
若变量内容从尾向前的数据符合“关键字”, 则将符合的最短数据删除
若变量内容从尾向前的数据符合“关键字”, 则将符合的最长数据删除
${变量/旧字串/新字串}
${变量//旧字串/新字串}
若变量内容符合“旧字串”则“第一个旧字串会被新字串取代”
若变量内容符合“旧字串”则“全部的旧字串会被新字串取代”

变量的测试与内容替换

范例一: 测试一下是否存在 username 这个变量, 若不存在则给予 username 内容为 root
[dmtsai@study ~]$ echo ${username}  <==由于出现空白, 所以 username 可能不存在, 也可能是空字串
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
root <==因为 username 没有设置, 所以主动给予名为 root 的内容。
[dmtsai@study ~]$ username="vbird tsai" <==主动设置 username 的内容
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
vbird tsai <==因为 username 已经设置了, 所以使用旧有的设置而不以 root 取代

重点在于减号“ - ”后面接的关键字! 基本上你可以这样理解:
new_var=${old_var-content}
新的变量, 主要用来取代旧变量。 新旧变量名称其实常常是一样的
new_var= ${ old_var-content }
这是本范例中的关键字部分! 必须要存在的!
new_var=${old_var-content}
旧的变量, 被测试的项目!
new_var=${old_var-content}
变量的“内容”, 在本范例中, 这个部分是在“给予未设置变量的内容”

因为 username 可能已经被设置为“空字串”。
果真如此的话, 那你还可以使用下面的方式来给予 username 的内容成为 root 。

范例二: 若 username 未设置或为空字串, 则将 username 内容设置为 root
[dmtsai@study ~]$ username=""
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
<==因为 username 被设置为空字串了! 所以当然还是保留为空字串!
[dmtsai@study ~]$ username=${username:-root}
[dmtsai@study ~]$ echo ${username}
root <==加上“ : ”后若变量内容为空或者是未设置, 都能够以后面的内容替换!

加上冒号后, 被测试的变量未被设置或者是已被设置为空字串时,
都能够用后面的内容 ( 本例中是使用 root 为内容) 来替换与设置。

总结:
var 与 str 为变量, 我们想要针对 str 是否有设置来决定 var 的值;
str: 代表“str 没设置或为空的字串时”; 至于 str 则仅为“没有该变量”。

变量设置方式 str 没有设置 str 为空字串 str 已设置非为空字串
var=$ var=exp var= var=$str
var=$ var=exp var=expr var=$str
var=$ var= var=expr var=expr
var=$ var= var= var=expr
var=$ str=expr var=expr str 不变 var= str 不变 var=$str
var=$ str=expr var=expr str=expr var=expr str 不变 var=$str
var=$ expr 输出至 stderr var= var=$str
var=$ expr 输出至 stderr expr 输出至 stderr var=$str

例子:

测试: 先假设 str 不存在 ( 用 unset) , 然后测试一下减号 ( -) 的用法:
[dmtsai@study ~]$ unset str; var=${str-newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=newvar, str= <==因为 str 不存在, 所以 var 为 newvar
测试: 若 str 已存在, 测试一下 var 会变怎样? :
[dmtsai@study ~]$ str="oldvar"; var=${str-newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=oldvar, str=oldvar <==因为 str 存在, 所以 var 等于 str 的内容

关于减号 ( -) :
个减号的测试并不会影响到旧变量的内容。 如果你想要将旧变量内容也一起替换掉的话, 那么就使用等号( =)

测试: 先假设 str 不存在 ( 用 unset) , 然后测试一下等号 ( =) 的用法:
[dmtsai@study ~]$ unset str; var=${str=newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=newvar, str=newvar<==因为 str 不存在, 所以 var/str 均为 newvar
测试: 如果 str 已存在了, 测试一下 var 会变怎样?
[dmtsai@study ~]$ str="oldvar"; var=${str=newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=oldvar, str=oldvar<==因为 str 存在, 所以 var 等于 str 的内容

如果只是想知道, 如果旧变量不存在时, 整个测试就告知我“有错误”, 此时就能够使用问号“ ? ”的帮忙。

测试: 若 str 不存在时, 则 var 的测试结果直接显示 "无此变量"
[dmtsai@study ~]$ unset str; var=${str?无此变量}
-bash: str: 无此变量 <==因为 str 不存在, 所以输出错误讯息
测试: 若 str 存在时, 则 var 的内容会与 str 相同!
[dmtsai@study ~]$ str="oldvar"; var=${str?novar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=oldvar, str=oldvar <==因为 str 存在, 所以 var 等于 str 的内容

命令别名与历史命令

命令别名设置: alias, unalias

指令别名设置
[dmtsai@study ~]$ alias lm='ls -al &#124; more'
查看指令别名
[dmtsai@study ~]$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias lm='ls -al | more'
alias ls='ls --color=auto'
alias rm='rm -i'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
取消别名
[dmtsai@study ~]$ unalias lm

历史命令: history

bash 有提供指令历史的服务

[dmtsai@study ~]$ history [n]
[dmtsai@study ~]$ history [-c]
[dmtsai@study ~]$ history [-raw] histfiles
选项与参数:
n  	: 数字, 意思是“要列出最近的 n 笔命令列表”的意思!
-c 	: 将目前的 shell 中的所有 history 内容全部消除
-a 	: 将目前新增的 history 指令新增入 histfiles 中, 若没有加 histfiles ,
则默认写入 ~/.bash_history
-r 	: 将 histfiles 的内容读到目前这个 shell 的 history 记忆中;
-w 	: 将目前的 history 记忆内容写入 histfiles 中!
范例一: 列出目前内存内的所有 history 记忆
[dmtsai@study ~]$ history
# 前面省略
1017 man bash
1018 ll
1019 history
1020 history
# 列出的信息当中, 共分两栏, 第一栏为该指令在这个 shell 当中的代码,
# 另一个则是指令本身的内容喔! 至于会秀出几笔指令记录, 则与 HISTSIZE 有关!
范例二: 列出目前最近的 3 笔数据
[dmtsai@study ~]$ history 3
1019 history
1020 history
1021 history 3
范例三: 立刻将目前的数据写入 histfile 当中
[dmtsai@study ~]$ history -w
# 在默认的情况下, 会将历史纪录写入 ~/.bash_history 当中!
[dmtsai@study ~]$ echo ${HISTSIZE}
1000

在正常的情况下, 历史命令的读取与记录是这样的:

  • 当我们以 bash 登陆 Linux 主机之后, 系统会主动的由主文件夹的 ~/.bash_history 读取以前曾经下过的指令, 那么 ~/.bash_history 会记录几笔数据呢? 这就与你 bash 的HISTFILESIZE 这个变量设置值有关了!
  • 假设我这次登陆主机后, 共下达过 100 次指令, “等我登出时, 系统就会将 101~1100 这总共 1000 笔历史命令更新到 ~/.bash_history 当中。 ” 也就是说, 历史命令在我登出时,会将最近的 HISTFILESIZE 笔记录到我的纪录档当中啦!
  • 当然, 也可以用 history -w 强制立刻写入的! 那为何用“更新”两个字呢? 因为~/.bash_history 记录的笔数永远都是 HISTFILESIZE 那么多, 旧的讯息会被主动的拿掉! 仅保留最新的!

history 这个历史命令不仅可以查询命令,还可以帮我们执行命令。

[dmtsai@study ~]$ !number
[dmtsai@study ~]$ !command
[dmtsai@study ~]$ !!
选项与参数:
number : 执行第几笔指令的意思;
command : 由最近的指令向前搜寻“指令串开头为 command”的那个指令, 并执行;
!! : 就是执行上一个指令( 相当于按↑按键后, 按 Enter)
[dmtsai@study ~]$ history
66 man rm
67 alias
68 man history
69 history
[dmtsai@study ~]$ !66 <==执行第 66 笔指令
[dmtsai@study ~]$ !! <==执行上一个指令, 本例中亦即 !66
[dmtsai@study ~]$ !al <==执行最近以 al 为开头的指令( 上头列出的第 67 个)

同一帐号同时多次登陆的 history 写入问题

bash 在同时以 同一个身份登陆,因此所有的 bash 都有自己的 1000 笔记录在内存中。
因为等到退出时才会更新记录文件, 所以, 最后登出的那个 bash 才会是最后写入的数据。
如此一来其他 bash 的指令操作就不会被记录下来了 ( 其实有被记录, 只是被后来的最后一个 bash 所覆盖更新) 。

无法记录时间

历史命令还有一个问题, 那就是无法记录指令下达的时间。 由于这 1000 笔历史命令是依序记录的, 但是并没有记录时间, 所以在查询方面会有一些不方便。

Bash Shell 的操作环境

路径与指令搜寻顺序

指令执行的顺序优先级)如下:
(以ls指令为例)

  1. 以相对/绝对路径执行指令, 例如“ /bin/ls ”或“ ./ls ”;
  2. 由 alias 找到该指令来执行
  3. 由 bash 内置的 ( builtin) 指令来执行
  4. 通过 $PATH 这个变量的顺序搜寻到的第一个指令来执行

如果想要了解指令搜寻的执行顺序, 其实通过 type -a ls 也可以查询得到。

[dmtsai@study ~]$ alias echo='echo -n'
[dmtsai@study ~]$ type -a echo
echo is aliased to `echo -n'
echo is a shell builtin
echo is /usr/bin/echo
## 可以清楚的看到先 alias 再 builtin(内建指令) 再由 $PATH 找到 /bin/echo

可以清楚的看到先 alias 再 builtin (内建指令)再由 $PATH 找到 /bin/echo

bash 的进站与欢迎信息: /etc/issue, /etc/motd

bash 可以设置登陆后进站画面与欢迎信息。
信息保存在 /etc/issue 里面

[dmtsai@study ~]$ cat /etc/issue
\S
Kernel \r on an \m
## 默认登陆画面

如同 $PS1 这变量一样, issue 这个文件的内容也是可以使用反斜线作为变量取用

issue 内的各代码意义
\d 本地端时间的日期;
\l 显示第几个终端机接口;
\m 显示硬件的等级 ( i386/i486/i586/i686...) ;
\n 显示主机的网络名称;
\O 显示 domain name;
\r 操作系统的版本 ( 相当于 uname -r)
\t 显示本地端时间的时间;
\S 操作系统的名称;
\v 操作系统的版本

除了 /etc/issue 之外还有个 /etc/issue.net ; 这个是提供给 telnet这个远端登陆程序用的。
当我们使用 telnet 连接到主机时, 主机的登陆画面就会显示/etc/issue.net 而不是 /etc/issue 。

至于如果您想要让使用者登陆后取得一些讯息, 例如您想要让大家都知道的讯息, 那么可以将讯息加入 /etc/motd 里面去。

[root@study ~]# vim /etc/motd
Hello everyone,
Our server will be maintained at 2015/07/10 0:00 ~ 24:00.
Please don't login server at that time. ^_^

那么当你的使用者( 包括所有的一般帐号与 root) 登陆主机后, 就会显示这样的讯息出来:

Last login: Wed Jul 8 23:22:25 2015 from 127.0.0.1
Hello everyone,
Our server will be maintained at 2015/07/10 0:00 ~ 24:00.
Please don't login server at that time. ^_^

bash 的环境配置文件

一进入 bash 就将取得一堆有用的变量
系统有一些环境设置文件的存在让 bash 在启动时直接读取这些配置文件,以规划好 bash 的操作环境
这些配置文件又可以分为全体系统的配置文件以及使用者个人偏好配置文件
要注意的是, 前几个小节谈到的命令别名、 自订的变量, 在你登出 bash 后就会失效, 所以你想要保留你的设置, 就得要将这些设置写入配置文件才行。

login 与 non-login shell

介绍 bash 的配置文件前, 我们一定要先知道的就是 login shellnon-login shell! 重点在于有没有登陆 ( login)
 

  • login shell取得 bash 时需要完整的登陆流程的, 就称为 login shell
    举例来说, 你要由tty1 ~ tty6 登陆, 需要输入使用者的帐号与密码, 此时取得的 bash 就称为“login shell”;
  • non-login shell: 取得 bash 接口的方法不需要重复登陆的举动;
    举例来说:
    ( 1) 你以 Xwindow 登陆 Linux 后, 再以 X 的图形化接口启动终端机, 此时那个终端接口并没有需要再次的输入帐号与密码, 那个 bash 的环境就称为 non-login shell了。
    ( 2) 你在原本的 bash 环境下再次下达 bash 这个指令, 同样的也没有输入帐号密码, 那第二个 bash( 子程序) 也是 non-login shell 。
     
    这两个取得 bash 的情况中, 读取的配置文件数据并不一样所致

一般来说, login shell 其实只会读取这两个配置文件:

  1. /etc/profile: 这是系统整体的设置, 你最好不要修改这个文件;
  2. ~/.bash_profile 或 ~/.bash_login 或 ~/.profile: 属于使用者个人设置, 你要改自己的数据, 就写入这里。

/etc/profile ( login shell 才会读):系统配置文件

这个配置文件可以利用使用者的识别码( 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 可不止会做这些事而已, 他还会去调用外部的设置数据,在 CentOS 7.x 默认的情况下, 下面这些数据会依序的被调用进来:

  • /etc/profile.d/*.sh
    其实这是个目录内的众多文件!
    只要在 /etc/profile.d/ 这个目录内且扩展名为 .sh , 另外, 使用者能够具有 r 的权限, 那么该文件就会被 /etc/profile 调用进来。 在 CentOS 7.x 中, 这个目录下面的文件规范了 bash 操作接口的颜色、 语系、 ll 与 ls 指令的命令别名、 vi 的命令别名、 which 的命令别名等等。 如果你需要帮所有使用者设置一些共享的命令别名时, 可以在这个目录下面自行创建扩展名为 .sh 的文件, 并将所需要的数据写入即可
  • /etc/locale.conf
    这个文件是由 /etc/profile.d/lang.sh 调用进来的! 这也是我们决定 bash 默认使用何种语系的重要配置文件! 文件里最重要的就是 LANG/LC_ALL 这些个变量的设置。
  • /usr/share/bash-completion/completions/*
    记得我们上头谈过 [tab] 的妙用吧? 除了命令补齐、 文件名补齐之外, 还可以进行指令的选项/参数补齐功能! 那就是从这个目录里面找到相对应的指令来处理的! 其实这个目录下面的内容是由 /etc/profile.d/bash_completion.sh 这个文件载入的。
     

总结:反正你只要记得, bash 的 login shell 情况下所读取的整体环境配置文件其实只有/etc/profile但是 /etc/profile 还会调用出其他的配置文件, 所以让我们的 bash 操作接口变的非常的友善。

~/.bash_profile ( login shell 才会读):个人偏好的配置文件

bash 在读完了整体环境设置的 /etc/profile 并借此调用其他配置文件后, 接下来则是会读取使用者的个人配置文件。 在 login shell 的 bash 环境中, 所读取的个人偏好配置文件其实主要有三个, 依序分别是:

  1. ~/.bash_profile
  2. ~/.bash_login
  3. ~/.profile

其实 bash 的 login shell 设置只会读取上面三个文件的其中一个, 而读取的顺序则是依照上面的顺序。
也就是说, 如果 ~/.bash_profile 存在, 那么其他两个文件不论有无存在, 都不会被读取。 如果 ~/.bash_profile 不存在才会去读取 ~/.bash_login, 而前两者都不存在才会读取~/.profile 的意思。 会有这么多的文件, 其实是因应其他 shell 转换过来的使用者的习惯而已。

[dmtsai@study ~]$ cat ~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then    <==下面这三行在判断并读取 ~/.bashrc
        . ~/.bashrc  		 <== source指令 : 读入文件的指令
fi

# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin    <==下面这几行在处理个人化设置
export PATH

这个文件内有设置 PATH 这个变量;
而且还使用了 export 将 PATH 变成环境变量;
由于PATH 在 /etc/profile 当中已经设置过, 所以在这里就以累加的方式增加使用者主文件夹下的~/bin/ 为额外的可执行文件放置目录。
这也就是说, 你可以将自己创建的可执行文件放置到你自己主文件夹下的 ~/bin/ 目录! 那就可以直接执行该可执行文件而不需要使用绝对/相对路径来执行该文件。

在bash_profile中会判断主文件夹下是否有 ~/.bashrc文件,也就是说 ~/.bash_profile 其实会再调用 ~/.bashrc 的设置内容。通过 source指令,读入环境配置文件

整个 login shell 的读取流程:
在这里插入图片描述
实线的的方向是主线流程, 虚线的方向则是被调用的配置文件;
在 CentOS 的 login shell 环境下, 最终被读取的配置文件是“ ~/.bashrc ”这个文件;
所以, 你当然可以将自己的偏好设置写入该文件即可。

source : 读入环境配置文件的指令

由于 /etc/profile 与 ~/.bash_profile 都是在取得 login shell 的时候才会读取的配置文件, 所以, 如果你将自己的偏好设置写入上述的文件后, 通常都是得登出再登陆后, 该设置才会生效。
那么, 能不能直接读取配置文件而不登出登陆呢? 可以的! 那就得要利用 source 这个指令了:

[dmtsai@study ~]$ source 配置文件文件名
范例: 将主文件夹的 ~/.bashrc 的设置读入目前的 bash 环境中
[dmtsai@study ~]$ source ~/.bashrc <==下面这两个指令是一样的!
[dmtsai@study ~]$ . ~/.bashrc

利用 source 或小数点 ( .) 都可以将配置文件的内容读进来目前的 shell 环境中;
举例来说, 我修改了 ~/.bashrc , 那么不需要登出, 立即以 source ~/.bashrc 就可以将刚刚最新设置的内容读进来目前的环境中。
还有, 包括 ~/bash_profile 以及 /etc/profile 的设置中, 很多时候也都是利用到这个 source ( 或小数点) 的功能。

~/.bashrc ( non-login shell 会读)

你取得 non-login shell 时, 该 bash 配置文件仅会读取 ~/.bashrc 而已:

[root@study ~]# cat ~/.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

特别注意一下, 由于 root 的身份与一般使用者不同, 鸟哥是以 root 的身份取得上述的数据,如果是一般使用者的 ~/.bashrc 会有些许不同。
 
你会发现在 root 的 ~/.bashrc 中其实已经规范了较为保险的命令别名了。
此外, CentOS 7.x 还会主动的调用 /etc/bashrc这个文件,因为 /etc/bashrc 帮我们的 bash 定义出下面的数据:

  • 依据不同的 UID 规范出 umask 的值;
  • 依据不同的 UID 规范出提示字符 ( 就是 PS1 变量) ;
  • 调用 /etc/profile.d/*.sh 的设置
     

你要注意的是, 这个 /etc/bashrc 是 CentOS 特有的 ( 其实是 Red Hat 系统特有的) , 其他不同的 distributions 可能会放置在不同的文件名就是了。

其他相关配置文件

事实上还有一些配置文件可能会影响到你的 bash 操作的:

  • /etc/man_db.conf
    这的文件的内容“规范了使用 man 的时候, man page 的路径到哪里去寻找! ”所以说的简单一点, 这个文件规定了下达 man 的时候, 该去哪里查看数据的路径设置。
    那么什么时候要来修改这个文件呢? 如果你是以 tarball 的方式来安装你的数据, 那么你的man page 可能会放置在 /usr/local/softpackage/man 里头, 那个 softpackage 是你的套件名称, 这个时候你就得以手动的方式将该路径加到 /etc/man_db.conf 里头, 否则使用 man 的时候就会找不到相关的说明文档。
  • ~/.bash_history
    默认的情况下, 我们的历史命令就记录在这里啊! 而这个文件能够记录几笔数据, 则与 HISTFILESIZE 这个变量有关啊。 每次登陆 bash后, bash 会先读取这个文件, 将所有的历史指令读入内存, 因此, 当我们登陆 bash 后就可以查知上次使用过哪些指令。
  • ~/.bash_logout
    这个文件则记录了“当我登出 bash 后, 系统再帮我做完什么动作后才离开”的意思。
    你可以去读取一下这个文件的内容, 默认的情况下, 登出时, bash 只是帮我们清掉屏幕的讯息而已。不过, 你也可以将一些备份或者是其他你认为重要的工作写在这个文件中 ( 例如清空暂存盘) , 那么当你离开 Linux 的时候, 就可以解决一些烦人的事情

终端机的环境设置: stty, set

在 tty1 ~ tty6 这六个命令行的终端机( terminal) 环境中登陆, 登陆的时候我们可以取得一些字符设置的功能;
 
举例来说, 我们可以利用倒退键 ( backspace, 就是那个←符号的按键) 来删除命令列上的字符也可以使用 [ctrl]+c 来强制终止一个指令的运行当输入错误时, 就会有声音跑出来警告

查阅目前的一些按键内容, 可以利用 stty ( setting tty 终端机的意思);
stty也可以帮助设置终端机的输入按键代表意义

[dmtsai@study ~]$ stty [-a]
选项与参数:
-a 	: 将目前所有的 stty 参数列出来;

范例一: 列出所有的按键与按键内容
[dmtsai@study ~]$ stty -a
speed 38400 baud; rows 20; columns 90; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
flush = ^O; min = 1; time = 0;
....(以下省略)....

^ 表示 [Ctrl] 那个按键的意思

几个重要的代表意义:

  • intr : 送出一个 interrupt ( 中断) 的讯号给目前正在 run 的程序 ( 就是终止啰! ) ;
  • quit : 送出一个 quit 的讯号给目前正在 run 的程序;
  • erase : 向后删除字符,
  • kill : 删除在目前命令行上的所有文字;
  • eof : End of file 的意思, 代表“结束输入”。
  • start : 在某个程序停止后, 重新启动他的 output
  • stop : 停止目前屏幕的输出;
  • susp : 送出一个 terminal stop 的讯号给正在 run 的程序。

除了 stty 之外, 其实我们的 bash 还有自己的一些终端机设置值;
那就是利用 set 来设置的;我们之前提到一些变量时, 可以利用 set 来显示, 除此之外, 其实 set 还可以帮我们设置整个指令输出/输入的环境。

[dmtsai@study ~]$ set [-uvCHhmBx]
选项与参数:
-u	 : 默认不启用。 若启用后, 当使用未设置变量时, 会显示错误讯息;
-v	 : 默认不启用。 若启用后, 在讯息被输出前, 会先显示讯息的原始内容;
-x	 : 默认不启用。 若启用后, 在指令被执行前, 会显示指令内容( 前面有 ++ 符号)
-h	 : 默认启用。 与历史命令有关;
-H	 : 默认启用。 与历史命令有关;
-m	 : 默认启用。 与工作管理有关;
-B	 : 默认启用。 与刮号 [] 的作用有关;
-C	 : 默认不启用。 若使用 &gt; 等, 则若文件存在时, 该文件不会被覆盖。

范例一: 显示目前所有的 set 设置值
[dmtsai@study ~]$ echo $-
himBH
# 那个 $- 变量内容就是 set 的所有设置啦! bash 默认是 himBH 喔!

范例二: 设置 "若使用未定义变量时, 则显示错误讯息"
[dmtsai@study ~]$ set -u
[dmtsai@study ~]$ echo $vbirding
-bash: vbirding: unbound variable
# 默认情况下, 未设置/未宣告 的变量都会是“空的”, 不过, 若设置 -u 参数,
# 那么当使用未设置的变量时, 就会有问题啦! 很多的 shell 都默认启用 -u 参数。
# 若要取消这个参数, 输入 set +u 即可!

范例三: 执行前, 显示该指令内容。
[dmtsai@study ~]$ set -x
++ printf '\033]0;%s@%s:%s\007' dmtsai study '~'    # 这个是在列出提示字符的控制码!
[dmtsai@study ~]$ echo ${HOME}
+ echo /home/dmtsai
/home/dmtsai
++ printf '\033]0;%s@%s:%s\007' dmtsai study '~'
# 看见否? 要输出的指令都会先被打印到屏幕上喔! 前面会多出 + 的符号!

bash 默认的组合键:

组合按键 执行结果
Ctrl + C 终止目前的命令
Ctrl + D 输入结束 ( EOF) , 例如邮件结束的时候;
Ctrl + M 就是 Enter 啦!
Ctrl + S 暂停屏幕的输出
Ctrl + Q 恢复屏幕的输出
Ctrl + U 在提示字符下, 将整列命令删除
Ctrl + Z “暂停”目前的命令

通配字符与特殊符号

一些常用的通配字符:

符号 意义
* 代表『 0 个到无穷多个』任意字符
? 代表『一定有一个』任意字符
[ ] 同样代表『一定有一个在括号内』的字符(非任意字符)。例如 [abcd] 代表『一定有一个字符, 可能是 a, b, c, d 这四个任何一个』
[ - ] 若有减号在中括号内时,代表『在编码顺序内的所有字符』。例如 [0-9] 代表 0 到 9 之间的所有数字,因为数字的语系编码是连续的!
[^ ] 若中括号内的第一个字符为指数符号 (^) ,那表示『反向选择』,例如 [^abc] 代表 一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。

一些常用的特殊字符:

符号 内容
# 批注符号:这个最常被使用在 script 当中,视为说明!在后的数据均不执行
\ 跳脱符号:将『特殊字符或通配符』还原成一般字符
| 管线 (pipe):分隔两个管线命令的界定(后两节介绍);
; 连续指令下达分隔符:连续性命令的界定 (注意!与管线命令并不相同)
~ 用户的家目录
$ 取用变数前导符:亦即是变量之前需要加的变量取代值
& 工作控制 (job control):将指令变成背景下工作
! 逻辑运算意义上的『非』 not 的意思!
/ 目录符号:路径分隔的符号
>, >> 数据流重导向:输出导向,分别是『取代』与『累加』
<, << 数据流重导向:输入导向 (这两个留待下节介绍)
' ' 单引号,不具有变量置换的功能 ($ 变为纯文本)
" " 具有变量置换的功能! ($ 可保留相关功能)
`` 两个『 ` 』中间为可以先执行的指令,亦可使用$( )
( ) 在中间为子 shell 的起始与结束
{} 在中间为命令区块的组合

理论上, 你的“文件名”尽量不要使用到上述的字符

数据流重导向

什么是数据流重导向

数据流重导向就是将某个指令执行后应该要出现在屏幕上的数据, 给他传输到其他的地方, 例如文件或者是设备 ( 例如打印机之类的)。

指令执行过程的数据传输情况:

我们执行一个指令的时候, 这个指令可能会由文件读入数据, 经过处理之后, 再将数据输出到屏幕上。
在上图当中, standard outputstandard 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) 分别传送到其他的文件或设备去, 而分别传送所用的特殊字符则如下所示:
  1. 标准输入 ( stdin) : 代码为 0 , 使用 < 或 << ;
  2. 标准输出 ( stdout) : 代码为 1 , 使用 > 或 >> ;
  3. 标准错误输出( stderr) : 代码为 2 , 使用 2> 或 2>> ;
     

关于 > 和 >>

  • 1> : 以覆盖的方法将“正确的数据”输出到指定的文件或设备上;
  • 1>>: 以累加的方法将“正确的数据”输出到指定的文件或设备上;
  • 2> : 以覆盖的方法将“错误的数据”输出到指定的文件或设备上;
  • 2>>: 以累加的方法将“错误的数据”输出到指定的文件或设备上;

/dev/null 垃圾桶黑洞设备与特殊写法

如果要将错误讯息忽略掉而不显示或储存,可以使用/dev/null (黑洞设备);
这个 /dev/null 可以吃掉任何导向这个设备的信息。

范例: 将错误的数据丢弃, 屏幕上显示正确的数据
[dmtsai@study ~]$ find /home -name .bashrc 2> /dev/null
/home/dmtsai/.bashrc <==只有 stdout 会显示到屏幕上, stderr 被丢弃了
如何将正确与错误数据通通写入同一个文件去: 使用 2>&1 或  &> 
范例五: 将指令的数据全部写入名为 list 的文件中
[dmtsai@study ~]$ find /home -name .bashrc > list 2> list <==错误
[dmtsai@study ~]$ find /home -name .bashrc > list 2>&1 <==正确
[dmtsai@study ~]$ find /home -name .bashrc &> list <==正确

standard input : < 与 <<

将原本需要由键盘输入的数据, 改由文件内容来取代

范例六: 利用 cat 指令来创建一个文件的简单流程
[dmtsai@study ~]$ cat > catfile
testing
cat file test
<==这里按下 [ctrl]+d 来离开
[dmtsai@study ~]$ cat catfile
testing
cat file test
## 由于加入 > 在 cat 后, 所以那个 catfile 会被主动的创建, 而内容就是刚刚键盘上面输入的那两行数据了

用纯文本文件取代键盘的输入, 用某个文件的内容来取代键盘的敲击

范例七: 用 stdin 取代键盘的输入以创建新文件的简单流程
[dmtsai@study ~]$ cat > catfile  < ~/.bashrc
[dmtsai@study ~]$ ll catfile ~/.bashrc
-rw-r--r--. 1 dmtsai dmtsai 231 Mar 6 06:06 /home/dmtsai/.bashrc
-rw-rw-r--. 1 dmtsai dmtsai 231 Jul 9 18:58 catfile
# 注意看, 这两个文件的大小会一模一样! 几乎像是使用 cp 来复制一般!

<< 他代表的是“结束的输入字符”的意思;
举例来讲: “我要用 cat 直接将输入的讯息输出到 catfile 中, 且当由键盘输入 eof 时, 该次输入就结束”。

[dmtsai@study ~]$ cat > catfile << "eof"
> This is a test.
> OK now stop
> eof <==输入这关键字, 立刻就结束而不需要输入 [ctrl]+d
[dmtsai@study ~]$ cat catfile
This is a test.
OK now stop <==只有这两行, 不会存在关键字那一行!
## 利用 << 右侧的控制字符, 我们可以终止一次输入, 而不必输入 [crtl]+d 来结束

总结:
命令输出重导向的作用:

  • 屏幕输出的信息很重要, 而且我们需要将他存下来的时候;
  • 背景执行中的程序, 不希望他干扰屏幕正常的输出结果时;
  • 一些系统的例行命令 ( 例如写在 /etc/crontab 中的文件) 的执行结果, 希望他可以存下来时;
  • 一些执行命令的可能已知错误讯息时, 想以“ 2> /dev/null ”将他丢掉时;
  • 错误讯息与正确讯息需要分别输出时。

命令执行的判断依据: ; , &&, ||

在某些情况下, 很多指令我想要一次输入去执行, 而不想要分次执行时;
方法一使用: shell script
方法二使用:一次输入多重具有条件(关联性)的指令

cmd ; cmd ( 不考虑指令相关性的连续指令下达)

在指令与指令中间利用分号 ( ;) 来隔开, 这样一来, 分号前的指令执行完后就会立刻接着执行后面的指令了。
但这种方式不具有条件选择性及指令之间没有关联性。

[root@study ~]# sync; sync; shutdown -h now

$? ( 指令回传值) 与 && 或 ||

两个指令之间要有关联性, 而这个关联性主要判断的地方就在于前一个指令执行的结果是否正确。
若前一个指令执行的结果为正确, 在 Linux 下面会回传一个 $? = 0 的值
通过借由“ && ”及“ || ”,根据回传值来判断后续的指令是否要执行

指令下达情况 说明
cmd1&&cmd2 1. 若 cmd1 执行完毕且正确执行( $?=0) , 则开始执行 cmd2。
2. 若 cmd1执行完毕且为错误 ( $?≠0) , 则 cmd2 不执行。
cmd1 || cmd2 1. 若 cmd1 执行完毕且正确执行( $?=0) , 则 cmd2 不执行。
2. 若 cmd1 执行完毕且为错误 ( $?≠0) , 则开始执行 cmd2。

管线命令 ( pipe)

管线命令“ | ”仅能处理经由前面一个指令传来的正确信息, 也就是 standard output 的信息, 对于 stdandard error 并没有直接处理的能力。
在这里插入图片描述

在每个管线后面接的第一个数据必定是“指令”。 而且这个指令必须要能够接受 standardinput 的数据才行, 这样的指令才可以是为“管线命令”, 例如 less, more, head, tail 等都是可以接受 standard input 的管线命令。
至于例如 ls, cp, mv 等就不是管线命令了! 因为 ls, cp,mv 并不会接受来自 stdin 的数据
也就是说, 管线命令主要有两个比较需要注意的地方:

  • 管线命令仅会处理 standard output, 对于 standard error output 会予以忽略
  • 管线命令必须要能够接受来自前一个指令的数据成为 standard input 继续处理才行。

注意:

如果你硬要让 standard error 可以被管线命令所使用 ,让 2>&1 加入指令中~就可以让 2> 变成 1> 。

撷取命令: cut, grep

将一段数据经过分析后, 取出我们所想要 ;

撷取讯息通常是针对“一行一行”来分析的, 并不是整篇讯息分析的 。

cut

这个指令可以将一段讯息的某一段给他“切”出来~ 处理的讯息是以“行”为单位  
[dmtsai@study ~]$ cut -d'分隔字符' -f fields <==用于有特定分隔字符
[dmtsai@study ~]$ cut -c 字符区间 <==用于排列整齐的讯息
选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;

范例一:将 PATH 变量取出,我要找出第五个路径。
[dmtsai@study ~]$ echo ${PATH}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 1 | 2 | 3 | 4 | 5 | 6 |
[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 5
# 如同上面的数字显示,我们是以『 : 』作为分隔,因此会出现 /home/dmtsai/.local/bin
# 那么如果想要列出第 3 与第 5 呢?,就是这样:

[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 3,5
范例二:将 export 输出的讯息,取得第 12 字符以后的所有字符串
[dmtsai@study ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
.....(其他省略).....

# 注意看,每个数据都是排列整齐的输出!如果我们不想要『 declare -x 』时,就得这么做:
[dmtsai@study ~]$ export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/home/dmtsai"
HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!

范例三:用 last 将显示的登入者的信息中,仅留下用户大名
[dmtsai@study ~]$ last
root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in
root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33)
root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16)
# last 可以输出『账号/终端机/来源/日期时间』的数据,并且是排列整齐的

[dmtsai@study ~]$ cut -d'分隔字符' -f fields <==用于有特定分隔字符
[dmtsai@study ~]$ cut -c 字符区间 <==用于排列整齐的讯息
选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;
范例一:将 PATH 变量取出,我要找出第五个路径。
[dmtsai@study ~]$ echo ${PATH}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 1 | 2 | 3 | 4 | 5 | 6 |
[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 5
# 如同上面的数字显示,我们是以『 : 』作为分隔,因此会出现 /home/dmtsai/.local/bin
# 那么如果想要列出第 3 与第 5 呢?,就是这样:

[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 3,5
范例二:将 export 输出的讯息,取得第 12 字符以后的所有字符串
[dmtsai@study ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 注意看,每个数据都是排列整齐的输出!如果我们不想要『 declare -x 』时,就得这么做:

[dmtsai@study ~]$ export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/home/dmtsai"
HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!

范例三:用 last 将显示的登入者的信息中,仅留下用户大名
[dmtsai@study ~]$ last
root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in
root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33)
root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16)
# last 可以输出『账号/终端机/来源/日期时间』的数据,并且是排列整齐的

cut 主要的用途在于将“同一行里面的数据进行分解! ”最常使用在分析一些数据或文字数据的时候!

这是因为有时候我们会以某些字符当作分区的参数, 然后来将数据加以切割, 以取得我们所需要的数据 。

cut在处理多空格相连的数据时, 可能会比较吃力一点, 所以某些时刻可能会使用下一章的 awk来取代的 。

grep

cut 是将一行讯息当中, 取出某部分我们想要的, 而 grep 则是分析一行讯息, 若当中有我们所需要的信息, 就将该行拿出来 。

[dmtsai@study ~]$ grep [-acinv] [--color=auto] '搜寻字符串' filename
选项与参数:
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字符串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行!
--color=auto :可以将找到的关键词部分加上颜色的显示喔!

范例一:将 last 当中,有出现 root 的那一行就取出来;
[dmtsai@study ~]$ last | grep 'root'
范例二:与范例一相反,只要没有 root 的就取出!
[dmtsai@study ~]$ last | grep -v 'root'
范例三:在 last 的输出讯息中,只要有 root 就取出,并且仅取第一栏
[dmtsai@study ~]$ last | grep 'root' |cut -d ' ' -f1
# 在取出 root 之后,利用上个指令 cut 的处理,就能够仅取得第一栏啰!

范例四:取出 /etc/man_db.conf 内含 MANPATH 的那几行
[dmtsai@study ~]$ grep --color=auto 'MANPATH' /etc/man_db.conf
....(前面省略)....
MANPATH_MAP /usr/games /usr/share/man
MANPATH_MAP /opt/bin /opt/man
MANPATH_MAP /opt/sbin /opt/man
# 神奇的是,如果加上 --color=auto 的选项,找到的关键词部分会用特殊颜色显示喔!

他支持的语法很多,比如正则表达式;

grep 可以解析一行文字, 取得关键字, 若该行有存在关键字, 就会整行列出来 ;

CentOS 7 当中, 默认的 grep 已经主动加上 --color=auto 在 alias 内了 。

排序命令: sort, wc, uniq

排序与统计指令

sort

帮我们进行排序, 而且可以依据不同的数据型态来排序! 例如数字与文字的排序就不一样。

此外, 排序的字符与语系的编码有关, 因此, 如果您需要排序时, 建议使用 LANG=C 来让语系统一, 数据排序比较好一些

[dmtsai@study ~]$ sort [-fbMnrtuk] [file or stdin]
选项与参数:
-f :忽略大小写的差异,例如 A 与 a 视为编码相同;
-b :忽略最前面的空格符部分;
-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
-n :使用『纯数字』进行排序(默认是以文字型态来排序的);
-r :反向排序;
-u :就是 uniq ,相同的数据中,仅出现一行代表;
-t :分隔符,预设是用 [tab] 键来分隔;
-k :以那个区间 (field) 来进行排序的意思

范例一:个人账号都记录在 /etc/passwd 下,请将账号进行排序。
[dmtsai@study ~]$ cat /etc/passwd | sort
abrt:x:173:173::/etc/abrt:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
alex:x:1001:1002::/home/alex:/bin/bash
# 鸟哥省略很多的输出~由上面的数据看起来, sort 是预设『以第一个』数据来排序,
# 而且默认是以『文字』型态来排序的喔!所以由 a 开始排到最后啰!

范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三栏来排序,该如何?
[dmtsai@study ~]$ cat /etc/passwd | sort -t ':' -k 3
root:x:0:0:root:/root:/bin/bash
dmtsai:x:1000:1000:dmtsai:/home/dmtsai:/bin/bash
alex:x:1001:1002::/home/alex:/bin/bash
arod:x:1002:1003::/home/arod:/bin/bash
# 看到特殊字体的输出部分了吧?怎么会这样排列啊?呵呵!没错啦~
# 如果是以文字型态来排序的话,原本就会是这样,想要使用数字排序:
# cat /etc/passwd | sort -t ':' -k 3 -n
# 这样才行啊!用那个 -n 来告知 sort 以数字来排序啊!

范例三:利用 last ,将输出的数据仅取账号,并加以排序
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort

uniq

仅将来重复的数据像是一行

[dmtsai@study ~]$ uniq [-ic]
选项与参数:
-i :忽略大小写字符的不同;
-c :进行计数
范例一:使用 last 将账号列出,仅取出账号栏,进行排序后仅取出一位;
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort | uniq
范例二:承上题,如果我还想要知道每个人的登入总次数呢?
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort | uniq -c
1
6 (unknown
47 dmtsai
4 reboot
7 root
1 wtmp
# 从上面的结果可以发现 reboot 有 4 次, root 登入则有 7 次!大部分是以 dmtsai 来操作!
# wtmp 与第一行的空白都是 last 的默认字符,那两个可以忽略的!

wc
帮我们计算输出的讯息的整体数据

[dmtsai@study ~]$ wc [-lwm]
选项与参数:
-l :仅列出行;
-w :仅列出多少字(英文单字);
-m :多少字符;
范例一:那个 /etc/man_db.conf 里面到底有多少相关字、行、字符数?
[dmtsai@study ~]$ cat /etc/man_db.conf | wc
131 723 5171
# 输出的三个数字中,分别代表: 『行、字数、字符数』
范例二:我知道使用 last 可以输出登入者,但是 last 最后两行并非账号内容,那么请问,
我该如何以一行指令串取得登入系统的总人次?
[dmtsai@study ~]$ last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | \
> grep -v 'unknown' |wc -l
# 由于 last 会输出空白行, wtmp, unknown, reboot 等无关账号登入的信息,因此,我利用
# grep 取出非空白行,以及去除上述关键词那几行,再计算行数,就能够了解啰!

双向重导向: tee

想个简单的东西, 我们由前一节知道 > 会将数据流整个传送给文件或设备, 因此我们除非去读取该文件或设备, 否则就无法继续利用这个数据流。 万一我想要将这个数据流的处理过程中将某段讯息存下来, 应该怎么做? 利用 tee 就可以。
tee 会同时将数据流分送到文件去与屏幕 ( screen) ; 而输出到屏幕的, 其实就是 stdout ,那就可以让下个指令继续处理!
在这里插入图片描述

[dmtsai@study ~]$ tee [-a] file
选项与参数:
-a :以累加 (append) 的方式,将数据加入 file 当中!

[dmtsai@study ~]$ last | tee last.list | cut -d " " -f1
# 这个范例可以让我们将 last 的输出存一份到 last.list 文件中;

[dmtsai@study ~]$ ls -l /home | tee ~/homefile | more
# 这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出讯息!

[dmtsai@study ~]$ ls -l / | tee -a ~/homefile | more
# 要注意! tee 后接的文件会被覆盖,若加上 -a 这个选项则能将讯息累加。

tee 可以让 standard output 转存一份到文件内并将同样的数据继续送到屏幕去处理!
这样除了可以让我们同时分析一份数据并记录下来之外, 还可以作为处理一份数据的中间暂存盘记录之用。

字符转换命令: tr, col, join, paste, expand

在 vim 程序编辑器当中, 提到过 DOS 断行字符与 Unix 断行字符的不同, 并且可以使用dos2unix 与 unix2dos 来完成转换。
下面这些命令是在管线中被用来字符转换命令

tr
tr 可以用来删除一段讯息当中的文字, 或者是进行文字讯息的替换

[dmtsai@study ~]$ tr [-ds] SET1 ...
选项与参数:
-d :删除讯息当中的 SET1 这个字符串;
-s :取代掉重复的字符!
范例一:将 last 输出的讯息中,所有的小写变成大写字符:
[dmtsai@study ~]$ last | tr '[a-z]' '[A-Z]'
# 事实上,没有加上单引号也是可以执行的,如:『 last | tr [a-z] [A-Z] 』
范例二:将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
[dmtsai@study ~]$ cat /etc/passwd | tr -d ':'
范例三:将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除
[dmtsai@study ~]$ cp /etc/passwd ~/passwd && unix2dos ~/passwd
[dmtsai@study ~]$ file /etc/passwd ~/passwd
/etc/passwd: ASCII text
/home/dmtsai/passwd: ASCII text, with CRLF line terminators <==就是 DOS 断行
[dmtsai@study ~]$ cat ~/passwd | tr -d '\r' > ~/passwd.linux
# 那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr
[dmtsai@study ~]$ ll /etc/passwd ~/passwd*
-rw-r--r--. 1 root root 2092 Jun 17 00:20 /etc/passwd
-rw-r--r--. 1 dmtsai dmtsai 2133 Jul 9 22:13 /home/dmtsai/passwd
-rw-rw-r--. 1 dmtsai dmtsai 2092 Jul 9 22:13 /home/dmtsai/passwd.linux
# 处理过后,发现文件大小与原本的 /etc/passwd 就一致了!

这个指令也可以写在“正则表达式”里头! 因为他也是由正则表达式的方式来取代数据的。

col
虽然 col 有他特殊的用途, 不过, 很多时候, 他可以用来简单的处理将 [tab] 按键取代成为空白键。

[dmtsai@study ~]$ col [-xb]
选项与参数:
-x :将 tab 键转换成对等的空格键
范例一:利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空白
[dmtsai@study ~]$ cat -A /etc/man_db.conf <==此时会看到很多 ^I 的符号,那就是 tab
[dmtsai@study ~]$ cat /etc/man_db.conf | col -x | cat -A | more
# 嘿嘿!如此一来, [tab] 按键会被取代成为空格键,输出就美观多了!

join
他是在处理两个文件之间的数据, 而且,主要是在处理“两个文件当中, 有 "相同数据" 的那一行, 才将他加在一起”

[dmtsai@study ~]$ join [-ti12] file1 file2
选项与参数:
-t :join 默认以空格符分隔数据,并且比对『第一个字段』的数据,
如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个!
-i :忽略大小写的差异;
-1 :这个是数字的 1 ,代表『第一个文件要用那个字段来分析』的意思;
-2 :代表『第二个文件要用那个字段来分析』的意思。

范例一:用 root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一栏
[root@study ~]# head -n 3 /etc/passwd /etc/shadow
==> /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
==> /etc/shadow <==
root:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:*:16372:0:99999:7:::
daemon:*:16372:0:99999:7:::
# 由输出的资料可以发现这两个文件的最左边字段都是相同账号!且以 : 分隔

[root@study ~]# join -t ':' /etc/passwd /etc/shadow | head -n 3
root:x:0:0:root:/root:/bin/bash:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:16372:0:99999:7:::
# 透过上面这个动作,我们可以将两个文件第一字段相同者整合成一列!
# 第二个文件的相同字段并不会显示(因为已经在最左边的字段出现了啊!)

范例二:我们知道 /etc/passwd 第四个字段是 GID ,那个 GID 记录在
/etc/group 当中的第三个字段,请问如何将两个文件整合?
[root@study ~]# head -n 3 /etc/passwd /etc/group
==> /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
==> /etc/group <==
root:x:0:
bin:x:1:
daemon:x:2:
# 从上面可以看到,确实有相同的部分喔!赶紧来整合一下!

[root@study ~]# join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
0:root:x:0:root:/root:/bin/bash:root:x:
1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:
2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:
# 同样的,相同的字段部分被移动到最前面了!所以第二个文件的内容就没再显示。
# 请读者们配合上述显示两个文件的实际内容来比对!

这个 join 在处理两个相关的数据文件时, 就很有用。
需要特别注意的是, 在使用 join 之前, 你所需要处理的文件应该要事先经过排序sort) 处理! 否则有些比对的项目会被略过

paste

这个 paste 就要比 join 简单多了! 相对于 join 必须要比对两个文件的数据相关性, paste 就直接“将两行贴在一起, 且中间以 [tab] 键隔开”而已。

[dmtsai@study ~]$ paste [-d] file1 file2
选项与参数:
-d :后面可以接分隔字符。预设是以 [tab] 来分隔的!
- :如果 file 部分写成 - ,表示来自 standard input 的资料的意思。

范例一:用 root 身份,将 /etc/passwd 与 /etc/shadow 同一行贴在一起
[root@study ~]# paste /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash root:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin bin:*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16372:0:99999:7:::
# 注意喔!同一行中间是以 [tab] 按键隔开的!

范例二:先将 /etc/group 读出(用 cat),然后与范例一贴上一起!且仅取出前三行
[root@study ~]# cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3
# 这个例子的重点在那个 - 的使用!那玩意儿常常代表 stdin 喔!

expand
这玩意儿就是在将 [tab] 按键转成空白键;他的方向指令为unexpand,将空白转成[tab] 。

[dmtsai@study ~]$ expand [-t] file
选项与参数:
-t :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空格键取代。
    我们也可以自行定义一个 [tab] 按键代表多少个字符呢!

范例一:将 /etc/man_db.conf 内行首为 MANPATH 的字样就取出;仅取前三行;
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3
MANPATH_MAP /bin /usr/share/man
MANPATH_MAP /usr/bin /usr/share/man
MANPATH_MAP /sbin /usr/share/man
# 行首的代表标志为 ^ ,这个我们留待下节介绍!先有概念即可!

范例二:承上,如果我想要将所有的符号都列出来?(用 cat)
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3 |cat -A
MANPATH_MAP^I/bin^I^I^I/usr/share/man$
MANPATH_MAP^I/usr/bin^I^I/usr/share/man$
MANPATH_MAP^I/sbin^I^I^I/usr/share/man$
# 发现差别了吗?没错~ [tab] 按键可以被 cat -A 显示成为 ^I

范例三:承上,我将 [tab] 按键设定成 6 个字符的话?
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3 | expand -t 6 - | cat -A
MANPATH_MAP /bin /usr/share/man$
MANPATH_MAP /usr/bin /usr/share/man$
MANPATH_MAP /sbin /usr/share/man$
123456123456123456123456123456123456123456123456...
# 仔细看一下上面的数字说明,因为我是以 6 个字符来代表一个 [tab] 的长度,所以,
# MAN... 到 /usr 之间会隔 12 (两个 [tab]) 个字符喔!如果 tab 改成 9 的话,
# 情况就又不同了!这里也不好理解~您可以多设定几个数字来查阅就晓得!

分区命令: split

他可以帮你将一个大文件, 依据文件大小或行数来分区, 就可以将大文件分区成为小文件。

[dmtsai@study ~]$ split [-bl] file PREFIX
选项与参数:
-b :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等;
-l :以行数来进行分区。
PREFIX :代表前导符的意思,可作为分区文件的前导文字。

范例一:我的 /etc/services 有六百多 K,若想要分成 300K 一个文件时?
[dmtsai@study ~]$ cd /tmp; split -b 300k /etc/services services
[dmtsai@study tmp]$ ll -k services*
-rw-rw-r--. 1 dmtsai dmtsai 307200 Jul 9 22:52 servicesaa
-rw-rw-r--. 1 dmtsai dmtsai 307200 Jul 9 22:52 servicesab
-rw-rw-r--. 1 dmtsai dmtsai 55893 Jul 9 22:52 servicesac
# 那个档名可以随意取的啦!我们只要写上前导文字,小文件就会以
# xxxaa, xxxab, xxxac 等方式来建立小文件的!

范例二:如何将上面的三个小文件合成一个文件,档名为 servicesback
[dmtsai@study tmp]$ cat services* >> servicesback
# 很简单吧?就用数据流重导向就好啦!简单!

范例三:使用 ls -al / 输出的信息中,每十行记录成一个文件
[dmtsai@study tmp]$ ls -al / | split -l 10 - lsroot
[dmtsai@study tmp]$ wc -l lsroot*
10 lsrootaa
10 lsrootab
4 lsrootac
24 total
# 重点在那个 - 啦!一般来说,如果需要 stdout/stdin 时,但偏偏又没有文件,
# 有的只是 - 时,那么那个 - 就会被当成 stdin 或 stdout

参数代换: xargs

以字面上的意义来看, x 是加减乘除的乘号, args 则是arguments ( 参数) 的意思, 所以说, 这个玩意儿就是在产生某个指令的参数的意思。
xargs 可以读入 stdin 的数据, 并且以空白字符或断行字符作为分辨, 将 stdin 的数据分隔成为 arguments
因为是以空白字符作为分隔, 所以, 如果有一些文件名或者是其他意义的名词内含有空白字符的时候, xargs 可能就会误判了

[dmtsai@study ~]$ xargs [-0epn] command
选项与参数:
-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等等字符时,这个 -0 参数
可以将他还原成一般字符。这个参数可以用于特殊状态喔!
-e :这个是 EOF (end of file) 的意思。后面可以接一个字符串,当 xargs 分析到这个字符串时,
就会停止继续工作!
-p :在执行每个指令的 argument 时,都会询问使用者的意思;
-n :后面接次数,每次 command 指令执行时,要使用几个参数的意思。
当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!

范例一:将 /etc/passwd 内的第一栏取出,仅取三行,使用 id 这个指令将每个账号内容秀出来
[dmtsai@study ~]$ id root
uid=0(root) gid=0(root) groups=0(root) 
# 这个 id 指令可以查询用户的 UID/GID 等信息

[dmtsai@study ~]$ id $(cut -d ':' -f 1 /etc/passwd | head -n 3)
# 虽然使用 $(cmd) 可以预先取得参数,但可惜的是, id 这个指令『仅』能接受一个参数而已!
# 所以上述的这个指令执行会出现错误!根本不会显示用户的 ID 啊!

[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | id
uid=1000(dmtsai) gid=1000(dmtsai) groups=1000(dmtsai),10(wheel) 
# 我不是要查自己啊!
# 因为 id 并不是管线命令,因此在上面这个指令执行后,前面的东西通通不见!只会执行 id!

[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs id
# 依旧会出现错误!这是因为 xargs 一口气将全部的数据通通丢给 id 处理~但 id 就接受 1 个啊最多!

[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id
uid=0(root) gid=0(root) groups=0(root)
uid=1(bin) gid=1(bin) groups=1(bin)
uid=2(daemon) gid=2(daemon) groups=2(daemon)
# 透过 -n 来处理,一次给予一个参数,因此上述的结果就 OK 正常的显示啰!

范例二:同上,但是每次执行 id 时,都要询问使用者是否动作?
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id
id root ?...y
uid=0(root) gid=0(root) groups=0(root)
id bin ?...y
.....(底下省略).....
# 呵呵!这个 -p 的选项可以让用户的使用过程中,被询问到每个指令是否执行!

范例三:将所有的 /etc/passwd 内的账号都以 id 查阅,但查到 sync 就结束指令串
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | xargs -e'sync' -n 1 id
# 仔细与上面的案例做比较。也同时注意,那个 -e'sync' 是连在一起的,中间没有空格键。
# 上个例子当中,第六个参数是 sync 啊,那么我们下达 -e'sync' 后,则分析到 sync 这个字符串时,
# 后面的其他 stdin 的内容就会被 xargs 舍弃掉了!

会使用 xargs 的原因是,** 很多指令其实并不支持管线命令, 因此我们可以通过 xargs 来提供该指令引用 standard input 之用**。

范例四:找出 /usr/sbin 底下具有特殊权限的档名,并使用 ls -l 列出详细属性
[dmtsai@study ~]$ find /usr/sbin -perm /7000 | xargs ls -l
-rwx--s--x. 1 root lock 11208 Jun 10 2014 /usr/sbin/lockdev
-rwsr-xr-x. 1 root root 113400 Mar 6 12:17 /usr/sbin/mount.nfs
-rwxr-sr-x. 1 root root 11208 Mar 6 11:05 /usr/sbin/netreport
.....(底下省略).....
# 聪明的读者应该会想到使用『 ls -l $(find /usr/sbin -perm /7000) 』来处理这个范例!
# 都 OK!能解决问题的方法,就是好方法!

关于减号 - 的用途

在管线命令当中, 常常会使用到前一个指令的 stdout 作为这次的 stdin ;
某些指令需要用到文件名称 ( 例如 tar) 来进行处理时, 该 stdin 与stdout 可以利用减号 "-" 来替代。

[root@study ~]# mkdir /tmp/homeback
[root@study ~]# tar -cvf - /home | tar -xvf - -C /tmp/homeback

上面这个例子是说: “我将 /home 里面的文件给他打包, 但打包的数据不是纪录到文件, 而是传送到 stdout; 经过管线后, 将 tar -cvf - /home 传送给后面的 tar -xvf - ”。 后面的这个 - 则是取用前一个指令的 stdout因此, 我们就不需要使用 filename

posted @ 2023-05-28 23:58  CD、小月  阅读(33)  评论(0编辑  收藏  举报