龙之介大人

bash和shell的入门上
6.1 认识BASH和Shell为何要学文字接口的 shell?文字接口的传输速度一定比较快, 而且,较 不容易出...
扫描右侧二维码阅读全文
17
2019/08

bash和shell的入门上

6.1 认识BASH和Shell

  • 为何要学文字接口的 shell?
文字接口的传输速度一定比较快, 而且,较 不容易出现断线或者是信息外流的问题,
  • 那么目前我们的 Linux (以 CentOS 7.x 为例) 有多少我们可以使用的 shells 呢?
/bin/sh (已经被 /bin/bash 所取代)
/bin/bash (就是 Linux 预设的 shell)
/bin/tcsh (整合 C Shell ,提供更多的功能)
/bin/csh (已经被 /bin/tcsh 所取代)
  • 虽然各家 shell 的功能都差不多,但是在某些语法的下达方面则有所不同,因此建议你还是得要选择 某一种 shell 来熟悉一下较佳。 Linux 预设就是使用 bash ,所以最初你只要学会 bash 就可以了

6.2 Bash shell的功能

既然 /bin/bash 是 Linux 预设的 shell , bash 是 GNU 计划中 重要的工具软件之一,目前也是 Linux distributions 的标准 shell 。 bash主要兼容于 sh ,并且依据一些使用者需求而加强的 shell版本。

6.2.1 bash 主要的优点有底下几个:

  • 命令编修能力 (history):
  1. 这么多的指令记录在哪里呢?在你的家目录内的 .bash_history.
  2. ~/.bash_history 记录的是前一次登入以前所执行过的指令, 而至于这一次登入所执行的指令都被暂 存在内存中,当你成功的注销系统后,该指令记忆才会记录到.bash_history当中
  • 命令与文件补全功能: ([tab] 按键的好处)
  1. [Tab] 接在一串指令的第一个字的后面,则为命令补全;
  2. [Tab] 接在一串指令的第二个字以后时,则为『文件补齐』!
  3. 若安装 bash-completion 软件,则在某些指令后面使用 [tab] 按键时,可以进行『选项/参数的补齐』功能!
  • 命令别名设定功能: (alias)
  1. alias lm='ls -al'
  • 工作控制、前景背景控制: (job control, foreground, background)
  1. 使用前、背景的控制可以让工作进行的更为顺利!
  2. 至于工作控制(jobs)的用途则更广,可以让我们随时将工作丢到背景中执行!而不怕不小心使用了 [Ctrl] + c 来停掉该程序!
  3. 此外,也可以在单一登录的环境中,达到多任务的目的呢!
  • 程序化脚本: (shell scripts)
  1. 在DOS年代还记得将一堆指令写在一起的所谓的『批处理文件』.
  2. 在 Linux 底下的 shell scripts 则发挥更为强大的功能,可以将你平时管理系统常需要下达的连续指令写成一个文件, 该文件并且 可以透过对谈交互式的方式来进行主机的侦测工作!也可以藉由 shell 提供的环境变量及相关指令来 进行设计.
  • 通配符: (Wildcard)
  1. 除了完整的字符串之外, bash 还支持许多的通配符来帮助用户查询与指令下达。
  2. 举例来说,想要 知道 /usr/bin 底下有多少以 X 为开头的文件吗?使用:『 ls -l /usr/bin/X* 』就能够知道.

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

  • 那我怎么知道这个指令是来自于外部指令(指的是其他非 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 内建?
[root@study ~]# type ls
ls 是 `ls --color=auto' 的别名  <==未加任何参数,列出 ls 的最主要使用情况
[root@study ~]# type -t ls
alias                            <==仅列出 ls 执行时的依据
[root@study ~]# type -a ls
ls 是 `ls --color=auto' 的别名     <==最先使用 aliase
ls 是 /usr/bin/ls                <==还有找到外部指令在 /bin/ls

# 那么 cd 呢?
[xiaoqi@study ~]$ type cd
cd 是 shell 内嵌
  • 这个 type 也可以用 来作为类似 which 指令的用途

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

范例:如果指令串太长的话,如何使用两行来输出?
[dmtsai@study ~]$ cp /var/spool/mail/root /etc/crontab\ 
> /etc/fstab /root
上面这个指令用途是将三个文件复制到 /root 这个目录下而已。不过,因为指令太长,于是利用『 [Enter] 』来将 [Enter] 这个按键『跳脱!』开来,让 [Enter] 按键不再具有『开始执行』的 功能!好让指令可以继续在下一行输入.
需要特别留意, [Enter] 按键是紧接着反斜杠 () 的,两者
中间没有其他字符。因为 仅跳脱『紧接着的下一个字符』而已
如果顺利跳脱 [Enter] 后,下一行最前面就会主动出现 > 的符号, 你可以继续输入指令啰!也就是
说,那个 > 是系统自动出现的.
  • 快速组合键
组合键功能与示范
[ctrl]+u/[ctrl]+k分别是从光标处向前删除指令串 ([ctrl]+u) 及向后删除指令串 ([ctrl]+k)。
[ctrl]+a/[ctrl]+e分别是让光标移动到整个指令串的最前面 ([ctrl]+a) 或最后面 ([ctrl]+e)。
总之,当我们顺利的在终端机 (tty) 上面登入后, Linux 就会依据 /etc/passwd 文件的设定给我们一 个 shell (预设是 bash),然后我们就可以依据上面的指令下达方式来操作 shell, 之后,我们就可以 透过 man 这个在线查询来查询指令的使用方式与参数说明.

6.5 Shell 的变量功能

变量是 bash 环境中非常重要的一个玩意儿,我们知道 Linux 是多人多任务的环境,每个人登入系 统都能取得一个 bash shell, 每个人都能够使用 bash 下达 mail 这个指令来收受『自己』的邮件等 等

6.5.1 变量的取用:echo

你可以利用 echo 这个指令来取用变量,但是,变量在被取用时,前面 必须要加上钱字号$才行.
[xiaoqi@study ~]$ echo $variable
[xiaoqi@study ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin
[xiaoqi@study ~]$ echo ${PATH} #使用这种方法获取变量比较规范
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin

6.5.2 变量设定规则

  1. 变量与变量内容以一个等号『=』来连结,如下所示: myname=xiaoqi
  2. 等号两边不能直接接空格符,如下所示为错误:myname = xiaoqi』或『myname=xiao qi错误设定.
  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/binPATH=${PATH}:/home/bin
  4. 若该变量需要在其他子程序执行,则需要以export来使变量变成环境变量:export PATH
  5. 通常大写字符为系统默认变量,自行设定变量可以使用小写字符,方便判断 (纯粹依照使用者兴趣与嗜好) ;

10.取消变量的方法为使用 unset :unset 变量名称例如取消 myname name的设定:unset myname

  • 变量命名示例
[xiaoqi@study ~]$ name=xiaoqi
[xiaoqi@study ~]$ name="xiao qi"
[xiaoqi@study ~]$ name=xiao\ qi

#我要在 PATH 这个变量当中『累加』:/home/dmtsai/bin 这个目录
[xiaoqi@study ~]$ PATH=$PATH:/home/xiaoqi/bin/
[xiaoqi@study ~]$ PATH="$PATH":/home/xiaoqi/bin/
[xiaoqi@study ~]$ PATH=${PATH}:/home/xiaoqi/bin/
# 以上三种格式在PATH里头的设定都是可以的

#我要将 name 的内容多出 "yes" 呢?
[xiaoqi@study ~]$ echo name="$name"yes
name=xiao qiyes
[xiaoqi@study ~]$ echo name=${name}yes
name=xiao qiyes    #常用这个设定方式.

# 如何让我刚刚设定的 name=xiaoqi 可以用在下个 shell 的程序?
[root@study xiaoqi]# name=xiaoqi #设定变量
[root@study xiaoqi]# bash    #进入到新的子程序
[root@study xiaoqi]# echo $name    #输出name变量
        <--并没有输出设定变量的内容
[root@study xiaoqi]# exit #退出子程序
exit
[root@study xiaoqi]# export name  #设置环境变量
[root@study xiaoqi]# bash        #再次进入到新子程序
[root@study xiaoqi]# echo $name    #再次输出name的内容
xiaoqi        <---此时正常输出变量内容
[root@study xiaoqi]# exit
  • 什么是『子程序』呢?
就是说,在我目前这个 shell 的情况下,去启用另一个新的 shell ,新的那个 shell 就是子程序啦!在一般的状态下,父程序的自定义变量是无法在子程序内使用的。但是透过 export 将变量变成环境变量后,就能够在子程序底下应用了!
  • 如何进入到您目前核心的模块目录?
# 方法一
[root@study xiaoqi]# cd /lib/modules/`uname -r`/kernel
[root@study kernel]# pwd
/lib/modules/3.10.0-327.el7.x86_64/kernel

#方法二
[root@study ~]# cd /lib/modules/$(uname -r)/kernel 
[root@study kernel]# pwd
/lib/modules/3.10.0-327.el7.x86_64/kernel
使用方法二比较规范
  1. 每个 Linux 都能够拥有多个核心版本,且几乎 distribution 的核心版本都不相同。
  2. 以 CentOS 7.1 (未更新前) 为例,他的预设核心版本是3.10.0-229.el7.x86_64 ,所以核心模块目录在

/lib/modules/3.10.0-229.el7.x86_64/kernel/内。也由于每个 distributions 的这个值都不相同,但是我 们却可以利用uname -r这个指令先取得版本信息。就可以透过上面指令当中的内含指令$(uname -r)先取得版本输出到 cd ... 那个指令当中,就能够顺利的进入目前核心的驱动程序所放置的目录!

6.5.3 unset的使用

[root@study kernel]# echo $name
xiaoqi
[root@study kernel]# unset name
[root@study kernel]# echo $name

[root@study kernel]# 
根据上面的案例你可以试试看!就可以了解变量的设定!这个是很重要的!请勤加练习! 其中,较为重要的一些特殊符号的使用!例如单引号、双引号、跳脱字符、钱字号、反单引号等等,

6.5.4 环境变量的功能

  • 环境变量可以帮我们达到很多功能~包括家目录的变换啊、提示字符的显示啊、执行文件搜寻的路径等等.
  • 既然环境变量有那么多的功能,目前我的shell环境中, 有多少默认的环境变量呢?我们可以利用两个指令来查阅,分别是envexport!
#列出目前的 shell 环境下的所有环境变量与其内容。
XDG_SESSION_ID=1
HOSTNAME=study.centos.xiaoqi    <---主机名
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash                    <---目前这个环境使用的shell是哪一个程序
TERM=xterm                        <---这个终端使用的是环境是什么类型
HISTSIZE=1000                    <---记录指令的笔数(Centos默认记录1000笔)
SSH_CLIENT=10.10.1.222 53851 22    <---ssh登录者IP和端口
SELINUX_USE_CURRENT_RANGE=        
OLDPWD=/root                    <---上一个工作目录的所在
SSH_TTY=/dev/pts/0                <---ssh链接使用的那个终端窗口
USER=xiaoqi                        <---使用者的名字
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;31:*.tgz=01;
31:*.arc=01;31:....                <---一些颜色的显示
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin:/home/xiaoqi/bin/:/home/xiaoqi/bin/:/home/xiaoqi/bin/
MAIL=/var/spool/mail/xiaoqi        <---这个用户所取用的 mailbox 位置
PWD=/lib/modules/                <---目前用户所在的工作目录 (利用 pwd 取出!)
LANG=zh_CN.UTF-8                    <---这个与语系有关,底下会再介绍!
SELINUX_LEVEL_REQUESTED=
HISTCONTROL=ignoredups
HOME=/root                        <---这个用户的家目录!
SHLVL=2
LOGNAME=xiaoqi                    <---登入者用来登入的账号名称
SSH_CONNECTION=10.10.1.222 53851 10.10.1.150 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/1000
_=/usr/bin/env                    <---上一次使用的指令的最后一个参数(或指令本身)
  • envenvironment(环境) 的简写啊,上面的例子当中,是列出来所有的环境变量.
  • 当然,如果使用 export 也会是一样的内容~ 只不过,export还有其他额外的功能.

各个变量名之间的关系

  • HOME
代表用户的家目录。还记得我们可以使用 cd ~ 去到自己的家目录吗?或者利用 cd 就可以直接回到用户家 目录了。
  • SHELL
告知我们,目前这个环境使用的 SHELL 是哪支程序? Linux 预设使用 /bin/bash .
  • HISTSIZE
这个与『历史命令』有关,亦即是, 我们曾经下达过的指令可以被系统记录下来,而记录的『笔数』则是由这个值来设定的。
  • MAIL
当我们使用 mail 这个指令在收信时,系统会去读取的邮件信箱文件 (mailbox)。
  • PATH
就是执行文件搜寻的路径啦~目录与目录中间以冒号(:)分隔,由于文件的搜寻是依序由 PATH 的变量内的 目录来查询,所以,目录的顺序也是重要的。
  • LANG
这个重要!就是语系数据啰~很多讯息都会用到他, 举例来说,当我们在启动某些 perl 的程序语言文件 时,他会主动的去分析语系数据文件, 如果发现有他无法解析的编码语系,可能会产生错误喔!一般来说, 我们中文编码通常是 zh_CN.GBK 或者是 zh_CN.UTF-8,这两个编码偏偏不容易被解译出来,所以,有的时候,可能需要修订一下语系数据。
  • RANDOM
这个玩意儿就是『随机随机数』的变量啦!目前大多数的 distributions 都会有随机数生成器,那就是 /dev/random 这个文件。
我们可以透过这个随机数文件相关的变量 ($RANDOM) 来随机取得随机数值喔。 在 BASH 的环境下,这个 RANDOM 变量的内容,介于 0~32767 之间,所以,你只要 echo $RANDOM 时, 系统就会主动的随机取出一个介于 0~32767 的数值。万一我想要使用 0~9 之间的数值呢?呵呵~利用 declare 宣告数值类型, 然后这样做就可以了:
[root@study kernel]# declare -i number=$RANDOM*10/32767;echo $number
6        <---此时会随机取出 0~9 之间的数值!

6.5.5 用set观察所有变量 (含环境变量与自定义变量)

bash 可不只有环境变量喔,还有一些与 bash 操作接口有关的变量,以及用户自己定义的变量存在 的。
那么这些变量如何观察呢?这个时候就得要使用 set 这个指令了。 set 除了环境变量之外, 还 会将其他在 bash 内的变量通通显示出来.
BASH=/usr/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=113                            <== 在目前的终端机环境下,使用的字段有几个字符长度                                
HISTFILE=/root/.bash_history        <== 历史命令记录的放置文件,隐藏档
HISTFILESIZE=1000                    <== 存起来(与上个变量有关)的文件之指令的最大纪录笔数。
HISTSIZE=1000                        <== 目前环境下,内存中记录的历史命令最大笔数。
IFS=$' \t\n'                        <== 预设的分隔符
LINES=32                            <== 目前的终端机下的最大行数
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 变量内容可能有些许的差异,不要紧,『习惯你自己的习惯』就好了。 你可以 用 man bash (注 3)去查询一下 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 时,提示字符为 # ,否则就是 $ ~

CentOS 预设的 PS1 内容

[root@study ~]# cd /home/
[root@study home]# PS1='[\u@\h \w \A #\#]\$ '
[root@study /home 01:01 #61]# 
# 看到了吗?提示字符变了!变的很有趣吧!其中,那个 #85 比较有趣

$:(关于本 shell 的 PID)

钱字号本身也是个变量喔!这个代表的是『目前这个 Shell 的线程代号』,亦即是所谓的 PID (Process ID)。
想要知道我们的 shell 的 PID , 就可以用: echo $$ 即可!出现的数字就是 PID 号码。

?:(关于上个执行指令的回传值)

问号也是一个特殊的变量?没错!在 bash 里面这个变量可重要的很! 这个变数是:『上 一个执行的指令所回传的值』, 上面这句话的重点是『上一个指令』与『回传值』两个地方。当 我们执行某些指令时, 这些指令都会回传一个执行后的代码。一般来说,如果成功的执行该指令, 则会回传一个 0 值,如果执行过程发生错误,就会回传『错误代码』才对!一般就是以非为 0 的 数值来取代。
[root@study ~]# echo  $SHELL
/bin/bash
[root@study ~]# echo $?
0
[root@study ~]# lsa
bash: lsa: 未找到命令...
[root@study ~]# echo $?
127
[root@study ~]# echo $?
0
#由于上一个指令是正常执行所以返回值也是0

6.5.6 OSTYPE, HOSTTYPE, MACHTYPE:(主机硬件与核心的等级)

目前个人计算机的 CPU 主要分 为 32/64 位,其中 32 位又可分为 i386, i586, i686,而 64 位则称为 x86_64。
由于不同等级的 CPU 指令集不太相同,因此你的软件可能会针对某些 CPU 进行优化,以求取较佳的软件性能。
所以软件就有 i386, i686 及 x86_64 之分。以目前 (2015) 的主流硬件来说,几乎都是 x86_64 的 天下!
因此 CentOS 7 开始,已经不支持 i386 兼容模式的安装光盘

6.5.7 export:自定义变量转成环境变量

谈了envset现在知道有所谓的环境变量与自定义变量,那么这两者之间有啥差异呢?其实这两 者的差异在于『 该变量是否会被子程序所继续引用』.
当你登入 Linux 并取得一个 bash 之后,你的 bash 就是一个独立的程序,这个程序的识别使用的是 一个称为程序标识符,被称为 PID 的就是。 接下来你在这个 bash 底下所下达的任何指令都是由这 个 bash 所衍生出来的,那些被下达的指令就被称为子程序了。
  • 如图所示:

如上所示,我们在原本的 bash 底下执行另一个 bash ,结果操作的环境接口会跑到第二个 bash 去(就是子程序), 那原本的 bash 就会在暂停的情况 (睡着了,就是 sleep)。整个指令运作的环境是实 线的部分!若要回到原本的 bash 去, 就只有将第二个 bash 结束掉 (下达 exit 或 logout) 才行。
这个程序概念与变量有啥关系呢?因为子程序仅会继承父程序的环境变量, 子程序不会继承父程序的自定义变量!所以你在原本 bash 的自定义变量在进入了子程序后就会消失不见,一直到你离开子程序并回到原本的父程序后,这个变量才会又出现!
换个角度来想,也就是说,如果我能将自定义变量变成环境变量的话,那不就可以让该变量值继续存在于子程序,此时,那个 export 指令就很有用!如你想要让该变量内容继续的在子程序中使用,那么就请执行:
[root@study ~]# export 变量名称
这东西用在『分享自己的变量设定给后来呼叫的文件或其他程序』,主控文 后面呼叫其他附属文件(类似函式的功能),但是主控文件与附属文件内都有相同的变量名称, 若 一再重复设定时,要修改也很麻烦,此时只要在原本的第一个文件内设定好『 export 变量 』, 后 面所呼叫的文件就能够使用这个变量设定了!而不需要重复设定,这非常实用于 shell script 当中.

如果仅下达 export 而没有接变量时,那么此时将会把所有的『环境变量』输出.

[root@study ~]# export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="study.centos.xiaoqi"
declare -x LANG="zh_CN.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"
declare -x LOGNAME="xiaoqi"
......
  • 那如何将环境变量转成自定义变量呢?可以使用declare.

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

目前大多数的 Linux distributions 已经都是支持日渐流行的万国码了,也都支持大部分的国家语系。 那么我们的 Linux 到底支持了多少的语系呢?
  • 这可以由 locale 这个指令来查询
[root@study ~]# locale -a | more -
aa_DJ
aa_DJ.iso88591
aa_DJ.utf8
aa_ER
aa_ER@saaho
aa_ER.utf8
aa_ER.utf8@saaho
...
  • 简体中文语系至少支持了两种以上的编码,那么我们如何修改这些编码呢?其实可以透过底下这些变量:
[root@study ~]# locale            <==后面不加任何选项与参数即可!
LANG=zh_CN.UTF-8                <==主语言的环境
LC_CTYPE="zh_CN.UTF-8"            <==字符(文字)辨识的编码
LC_NUMERIC="zh_CN.UTF-8"        <==数字系统的显示讯息
LC_TIME="zh_CN.UTF-8"            <==时间系统的显示数据
LC_COLLATE="zh_CN.UTF-8"        <==字符串的比较与排序等
LC_MONETARY="zh_CN.UTF-8"        <==币值格式的显示等
LC_MESSAGES="zh_CN.UTF-8"        <==讯息显示的内容,如菜单、错误讯息等
......
LC_ALL=                            <==整体语系的环境
基本上,你可以逐一设定每个与语系有关的变量数据,但事实上,如果其他的语系变量都未设定, 且你有设定LANG或者是LC_ALL时,则其他的语系变量就会被这两个变量所取代!这也是为什么我们在 Linux 当中,通常说明仅设定LANGLC_ALL这两个变量而已,因为他是最主要的设定 变量!

为什么在 Linux 主机的终端机接口 (tty1 ~ tty6) 的环境 下,如果设定『 LANG=zh_TW.utf8 』这个设定值生效后,使用 man 或者其他讯息输出时, 都会 有一堆乱码,尤其是使用 ls -l 这个参数时?

因为在 Linux 主机的终端机接口环境下是无法显示像中文这么复杂的编码文字,所以就会产生乱码了。也就是如此,我们才会必须要在 tty1 ~ tty6 的环境下,加装一些中文化接口的软件,才能够看到中文!不过,如果你是在 MS Windows 主机以远程联机服务器的软件联机到主机的话,那么,其实文字接口确实是可以看到中文的。此时反而你得要在 LC_ALL 设定中文编码才好!

  • 无论如何,如果发生一些乱码的问题,那么设定系统里面保有的语系编码
例如: en_US 或 en_US.utf8 等等的设定,应该就 OK了

那么系统默认支持多少种语系呢? 当我们使用 locale
时,系统是列出目前 Linux 主机内保有的语系文件, 这些语系文件都放置在: /usr/lib/locale/ 这个目录中。

你当然可以让每个使用者自己去调整自己喜好的语系,但是整体系统默认的语系定义在哪里呢? 其实就是在 /etc/locale.conf

[root@study ~]# cat /etc/locale.conf 
LANG="zh_CN.UTF-8"

6.5.9 变量的有效范围

变量也有使用的『范围』?没错~我们在上头的 export 指令说明中,就提到了这个概念了。如果在跑程序的时候,有父程序与子程序的不同程序关系时,则『变量』可否被引用与 export 有关。 被 export 后的变量,我们可以称他为『环境变量』! 环境变量可以被子程序所引用,但是其他的自定义变量内容就不会存在于子程序中。
  • 环境变量=全局变量
  • 自定义变数=局部变量
为什么环境变量的数据可以被子程序所引用呢?
  1. 当启动一个 shell,操作系统会分配一记忆区块给 shell 使用,此内存内之变量可让子程序取用
  2. 若在父程序利用 export 功能,可以让自定义变量的内容写到上述的记忆区块当中(环境变量);
  3. 当加载另一个 shell 时 (亦即启动子程序,而离开原本的父程序了),子 shell 可以将父 shell 的环境变量所在的记忆区块导入自己的环境变量区块当中。
透过这样的关系,我们就可以让某些变量在相关的程序之间存在,以帮助自己更方便的操作环境喔!不过要提醒的是,这个『环境变量』与『bash 的操作环境』意思不太一样,举例来说, PS1 并不是环境变量, 但是这个 PS1 会影响到 bash 的接口 (提示字符)!

6.5.10 变量键盘读取、数组与声明: read, array, declare

我们上面提到的变量设定功能,都是由指令列直接设定的,那么,可不可以让用户能够经由键盘输入?

什么意思呢?是否记得某些程序执行的过程当中,会等待使用者输入 "yes/no" 之类的讯息?

在 bash 里面也有相对应的功能喔!此外,我们还可以宣告这个变量的属性,例如:数组或者是数字等等.

read

要读取来自键盘输入的变量,就是用 read 这个指令了。这个指令最常被用在 shell script 的撰写当中.
[dmtsai@study ~]$ read [-pt] variable
选项与参数:
-p :后面可以接提示字符!
-t :后面可以接等待的『秒数!』这个比较有趣~不会一直等待使用者啦!

# 让用户由键盘输入一内容,将该内容变成名为 atest 的变量
[root@study ~]# read atest
this is a test
[root@study ~]# echo ${atest}
this is a test

# 提示使用者 30 秒内输入自己的大名,将该输入字符串作为名为 named 的变量内容
[root@study ~]# read -p "please keyin your name: " -t 30 named
please keyin your name: xiaoqi
[root@study ~]# echo ${named}
xiaoqi
read 之后不加任何参数,直接加上变量名称,那么底下就会主动出现一个空白行等待你的输入(如范 例一)。

如果加上 -t 后面接秒数,例如上面的范例二,那么 30 秒之内没有任何动作时, 该指令就会自动略过了~

如果是加上-p!在输入的光标前就会有比较多可以用的提示字符给我们参考!

declare/typeset

declaretypeset是一样的功能,就是在『宣告变量的类型』。

如果使用declare后面并没有接任何参数,那么bash就会主动的将所有的变量名称与内容通通叫出来,就好像使用set一样.

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

# 让变量 sum 进行 100+300+50 的加总结果
[root@study ~]# sum=100+300+50
[root@study ~]# echo ${sum}
100+300+50        <---这是字符型的相加

[root@study ~]# declare -i sum=100+3+50  <---整数型的相加
[root@study ~]# echo ${sum}            
153
  • 由于在默认的情况底下, bash 对于变量有几个基本的定义:
变量类型默认为『字符串』,所以若不指定变量类型,则 1+2 为一个『字符串』而不是『计算式』。 所以 上述第一个执行的结果才会出现那个情况的;

bash 环境中的数值运算,预设最多仅能到达整数形态,所以 1/3 结果是 0;

# 将 sum 变成环境变量
[root@study ~]# declare -x sum
[root@study ~]# export | grep sum
declare -ix sum="153"        <---环境变量包括了 i 与 x 的定义

#让 sum 变成只读属性,不可更动!
[root@study ~]# declare -r sum
[root@study ~]# sum=tesgting
bash: sum: 只读变量

#让 sum 变成非环境变量的自定义变量!
[root@study ~]# declare +x sum    <---将 - 变成 + 可以进行『取消』动作
[root@study ~]# declare -p sum    <----p 可以单独列出变量的类型
declare -ir sum="153"    <---只剩下 i, r 的类型,不具有 x.
declare 也是个很有用的功能~尤其是当我们需要使用到底下的数组功能时,他也可以帮我们宣告数组的属性!

不过,老话一句,数组也是在 shell script 比较常用! 比较有趣的是,如果你不小 心将变量设定为『只读』,通常得要注销再登入才能复原该变量的类型了.

数组 (array) 变量类型

某些时候,我们必须使用数组来宣告一些变量,这有什么好处啊?在一般人的使用上, 果然是看不 出来有什么好处的

不过,如果您曾经写过程序的话,那才会比较了解数组的意义~ 数组对写数值 程序的设计师来说,可是不能错过学习的重点之一.

  • 在 bash 里头,数组的设定方式是:var[index]=content
意思是说,我有一个数组名为 var ,而这个数组的内容为 var[1]=小明,var[2]=大明,var[3]=好明 .... 等等,那个 index 就是一些数字,重点是用中刮号 ([ ]) 来设定的。 目前我们 bash 提供的是一 维数组。

如果您不必写一些复杂的程序, 那么这个数组的地方,可以先略过,等到有需要 再来学习即可!因为要制作出数组, 通常与循环或者其他判断式交互使用才有比较高的存在意义!

#设定上面提到的 var[1] ~ var[3] 的变数。
[root@study ~]# echo "${var[1]},${var[2]},${var[3]}"
small min,big min,nice min
数组的变量类型比较有趣的地方在于『读取』,一般来说,建议直接以 ${数组} 的方式来读取,比较正确无误的.

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

想象一个状况:我的 Linux 主机里面同时登入了十个人,这十个人不知怎么搞的, 同时开启了 100 个文件,每个文件的大小约 10MBytes ,请问一下, 我的 Linux 主机的内存要有多大才够?

10*100*10 = 10000 MBytes = 10GBytes ... 为了要预防这个情况 的发生,所以我们的 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)数量。

#列出你目前身份(假设为一般账号)的所有限制数据数值
[root@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) 7240
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) 7240
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

# 限制用户仅能建立 10MBytes 以下的容量的文件
[root@study ~]# ulimit -f 10240
[root@study ~]# ulimit -a | grep 'file size'
core file size          (blocks, -c) 0
file size               (blocks, -f) 10240    <--最大量为 10240Kbyes,相当 10Mbytes
[root@study ~]# dd if=/dev/zero of=123 bs=1M count=20 
文件大小超出限制(吐核)        <--尝试建立20M文件,报文件超出大小

[root@study ~]# rm 123
rm:是否删除普通文件 "123"?y    <--删除文件,同时你得要注销再次的登入才能解开 10M 的限制
单一 filesystem 能够支持的单一文件大小与 block 的大小有关。但是文件系统的限制容量都允许的太大了!

如果想要让使用者建立的文件不要太大时,我们是可以考虑用 ulimit 来限制使用者可以建立的文件大小!利用 ulimit -f 就可以来设定了!例如上面的范例二,要注意单位喔!单位是 Kbytes。

想要复原 ulimit 的设定最简单的方法就是注销再登入,否则就是得要重新以 ulimit 设定才行! 不过,要注意的是,一般身份使用者如果以 ulimit 设定了 -f 的文件大小,那么他只能继续减小文件 容量,不能增加文件容量喔!

若想要管控使用者的 ulimit 限值,可以使用pam

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

变量内容的删除

变量的内容可以很简单的透过几个咚咚来进行删除喔!我们使用 PATH 这个变量的内容来做测试好 了。
# 先让小写的 path 自定义变量设定的与 PATH 内容相同
[root@study ~]# path=${PATH}
[root@study ~]# echo ${path}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin

#假设我不喜欢 local/bin,所以要将前 1 个目录删除掉,如何显示?
[root@study ~]# echo ${path#/*local/bin:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin
  • 上面这个范例很有趣的!他的重点可以用底下示例来说明:
${variable#/*local/bin:}上面的特殊字体部分是关键词!用在这种删除模式所必须存在的
${variable#/*local/bin:}这就是原本的变量名称,以上面范例二来说,这里就填写 path 这个『变量名称』!
${variable#/*local/bin:}这是重点!代表『从变量内容的最前面开始向右删除』,且仅删除最短的那个
${variable#/*local/bin:}代表要被删除的部分,由于 # 代表由前面开始删除,所以这里便由开始的 / 写起。 需要注意的是,我们还可以透过通配符 * 来取代 0 到无穷多个任意字符
以上面范例的结果来看, path 这个变量被删除的内容如下所示:

/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

  • 这样了解了 # 的功能了,接下来让我们来看看底下的范例三
#我想要删除前面所有的目录,仅保留最后一个目录
[root@study ~]# echo ${path#/*:}     
/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin
# 由于一个 # 仅删除掉最短的那个,因此他删除的情况可以用底下的删除线来看:
# ~~/usr/local/bin:~~/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin

[root@study ~]# echo ${path##/*:}
/home/xiaoqi/bin
# 嘿!多加了一个 # 变成 ## 之后,他变成『删除掉最长的那个数据』!亦即是:
# ~~/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:~~/home/xiaoqi/bin
因为在 PATH 这个变量的内容中,每个目录都是以冒号『:』隔开的,所以要从头删除掉目录就是介于斜线 (/) 到冒号 (:) 之间的数据!但是 PATH 中不止一个冒号 (:)! 所 以 # 与 ## 就分别代表:
  • # :符合取代文字的『最短的』那一个;
  • ##:符合取代文字的『最长的』那一个
上面谈到的是『从前面开始删除变量内容』,那么如果想要『从后面向前删除变量内容』呢? 这个 时候就得使用百分比 (%) 符号.
  • 来看看范例四怎么做
#我想要删除最后面那个目录,亦即从 : 到 bin 为止的字符串
[root@study ~]# echo ${path%:*bin}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin

# 注意啊!最后面一个目录不见了!
# 这个 % 符号代表由最后面开始向前删除!所以上面得到的结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:~~/home/xiaoqi/bin~~

# 那如果我只想要保留第一个目录呢?
[root@study ~]# echo ${path%%:*bin}
/usr/local/bin
# 同样的, %% 代表的则是最长的符合字符串,所以结果其实是来自如下:
# /usr/local/bin~~:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin~~
由于我是想要由变量内容的后面向前面删除,而我这个变量内容最后面的结尾是『/home/dmtsai/bin』.

所以可以看到上面我删除的数据最终一定是『bin』,亦即是『:bin』那个 代表通配符! 至于 % 与 %% 的意义其实与 # 及 ## 类似!

例题:

假设你是 xiaoqi ,那你的 MAIL 变量应该是 /var/spool/mail/xiaoqi 。假设你只想要保留最后面那个档名 (xiaoqi), 前面的目录名称都不要了,如何利用 $MAIL 变量来达成?

题意其实是这样『/var/spool/mail/xiaoqi』,亦即删除掉两条斜线间的所有数据(最长符合)。 这个时候你就可 以这样做即可:

[root@study ~]# echo ${MAIL##/*/}    
xiaoqi

相反的,如果你只想要拿掉文件名,保留目录的名称,亦即是『/var/spool/mail/xiaoqi』 (最短符合)。但假设 你并不知道结尾的字母为何,此时你可以利用通配符来处理即可,如下所示:

[root@study ~]# echo ${MAIL%/*}  
/var/spool/mail

变量内容的取代

接上以上范例.
#将 path 的变量内容内的 sbin 取代成大写 SBIN:
[root@study ~]# echo ${path/sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin
# 这个部分就容易理解的多了!关键词在于那两个斜线,两斜线中间的是旧字符串
# 后面的是新字符串,所以结果就会出现如上述的特殊字体部分!

[root@study ~]# echo ${path//sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/xiaoqi/.local/bin:/home/xiaoqi/bin
# 如果是两条斜线,那么就变成所有符合的内容都会被取代!
  • 我们将这部份作个总结说明一下:
变量设定方式说明
${变量#关键词}若变量内容从头开始的数据符合『关键词』,则将符合的最短数据删除
${变量##关键词}若变量内容从头开始的数据符合『关键词』,则将符合的最长数据删除
${变量%关键词}若变量内容从尾向前的数据符合『关键词』,则将符合的最短数据删除
${变量%%关键词}若变量内容从尾向前的数据符合『关键词』,则将符合的最长数据删除
${变量/旧字符串/新字符串}若变量内容符合『旧字符串』则『第一个旧字符串会被新字符串取代』
${变量//旧字符串/新字符串}若变量内容符合『旧字符串』则『全部的旧字符串会被新字符串取代』

变量的测试与内容替换

在某些时刻我们常常需要『判断』某个变量是否存在,若变量存在则使用既有的设定,若变量不存在则给予一个常用的设定。
  • 我们举底下的例子来说明"
#测试一下是否存在 username 这个变量,若不存在则给予 username 内容为 root
[root@study ~]# echo ${usename}
        <==由于出现空白,所以 username 可能不存在,也可能是空字符串
[root@study ~]# usename=${usename-root}
[root@study ~]# echo ${usename}
root    <==因为 username 没有设定,所以主动给予名为 root 的内容。

[root@study ~]# usename="xiaoqi"    <==主动设定 username 的内容
[root@study ~]# usename=${usename-root}
[root@study ~]# echo ${usename}
xiaoqi         <==因为 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
[root@study ~]# usename=""
[root@study ~]# usename=${usename-root}
[root@study ~]# echo ${usename}
        <==因为 username 被设定为空字符串了!所以当然还是保留为空字符串!
[root@study ~]# usename=${usename:-root}
[root@study ~]# echo ${usename}         
root    <==加上『 : 』后若变量内容为空或者是未设定,都能够以后面的内容替换!
在大括号内有没有冒号『 : 』的差别是很大的!加上冒号后,被测试的变量未被设定或者是已被设 定为空字符串时, 都能够用后面的内容 (本例中是使用 root 为内容) 来替换与设定

变量设定表

变量设定方式str 没有设定str 为空字符串str 已设定非为空字符串
var=${str-expr}var=exprvar=var=$str
var=${str:-expr}var=exprvar=exprvar=$str
var=${str+expr}var=var=exprvar=expr
var=${str:+expr}var=var=var=expr
var=${str=expr}str=expr var=exprstr 不变 var=str 不变 var=$str
var=${str:=expr}str=expr var=exprstr=expr var=exprstr 不变 var=$str
var=${str?expr}expr 输出至 stderrvar=var=$str
var=${str:?expr}expr 输出至 stderrexpr 输出至 stderrvar=$str
最后修改:2019 年 08 月 17 日 04 : 23 PM

发表评论