Bonky Zhu
If someone is able to show me that what I think or do is not right, I will happily change, for I seek the truth, by which no one was ever truly harmed. It is the person who continues in his self-deception and ignorance who is harmed.

Linux Bash环境

功能

历史记录

bash 可以记录你曾经使用过的指令。你使用过的指令都被存放在 .bash_history 中。.bash_history 记录的是前一次登入以前所执行过的指令, 而至于这一次登入所执行的指令都被暂存在内存中,当你成功的注销系统后,该指令记忆才会记录到 .bash_history 当中

$ history [n]   # 展示最近 n 条
$ history -c # 清除所有历史
$ history -w # 手动写入 history

$ echo ${HISTSIZE} # HISTSIZE 决定存储的历史指令条数
1000


同一用户多开 bash 的问题

同时开启多个 shell, 因此所有的 bash 都有自己的 1000 笔记录在内存中。因为等到注销时才会更新记录文件,所以, 最后注销的那个 bash 才会是最后写入的数据。如此一来其他 bash 的指令操作被最后一个 bash 所覆盖更新了。

执行以前的命令

$ !66 <==执行第 66 笔指令
$ !! <==执行上一个指令,本例中亦即 !66
$ !ec <==执行最近以 ec 为开头的指令


内置关键字

builtin, !, %, ., :, @, {, }, alias, alloc, bg, bind, bindkey, break, breaksw, builtins, case, cd, chdir, command, complete, continue, default, dirs, do, done, echo, echotc, elif, else, end, endif, endsw, esac, eval, exec, exit, export, false, fc, fg, filetest, fi, for, foreach, getopts, glob, goto, hash, hashstat, history, hup, if, jobid, jobs, kill, limit, local, log, login, logout, ls-F, nice, nohup, notify, onintr, popd, printenv, pushd, pwd, read, readonly, rehash, repeat, return, sched, set, setenv, settc, setty, setvar, shift, source, stop, suspend, switch, telltc, test, then, time, times, trap, true, type, ulimit, umask, unalias, uncomplete, unhash, unlimit, unset, unsetenv, until, wait, where, which, while -- shell built-in commands


介绍一个很有用的指令 type,他可以判断各个指令是否为内置的指令,还可以找到指令的位置。

指令串分行

[dmtsai@study ~]$ cp /var/spool/mail/root /etc/crontab \
> /etc/fstab /root


只要在反斜杠后面紧跟回车即可触发指令串分行输入(中间不能输入别的东西)。

别名

设置别名和取消别名分别为 alias, unalias。如果你想查看所有的 alias,请直接输入 alias

变量

查看变量

使用 export 或者 echo $<变量名 > 都是可以的

$ export # 显示所有的环境变量
declare -x HISTSIZE="3000"
declare -x HISTTIMEFORMAT="%F %T "
declare -x HOME="/root"
declare -x HOSTNAME="VM_0_3_centos"
declare -x LANG="zh_CN.UTF-8"
.....

$ echo $MAIL # 读取邮箱地址
/var/spool/mail/root


如果你想要查看所有的环境变量,请使用 exportenv,如果你想查看所有的变量(包括自定义变量)请使用 set

设定规则

  1. 变量的赋值使用的是等号,而且等号两边不能有空格,所以说 a = b 是错误的写法,a=b 才对
  2. 变量名称只能是英文字母与数字,但是开头字符不能是数字
  3. 变量内容若有空格符应该用单引号或者双引号括起来。单引号和双引号在没有特殊符号的时候是没有区别的,但不过在有特殊符号的时候我们需要注意:
    1. 双引号内的特殊字符如 \$ 等,可以保有原本的特性:"lang is $LANG" = "lang is zh_CN.UTF-8"
    2. 单引号内的特殊字符则仅为纯文本
  4. 可用转义字符 \ 将特殊符号(如 [Enter], $, \, 空格符, '等)变成一般字符
  5. 需要引用其它指令的结果时候使用 `指令` 和 $(指令)
  6. $ echo `uname -r`
  7. 3.10.0-957.21.3.el7.x86_64
  8. $ echo uname -r
  9. uname -r
  10. $ echo $(uname -r)
  11. 3.10.0-957.21.3.el7.x86_64
  12. $ echo (uname -r)
  13. -bash: 未预期的符号 `uname' 附近有语法错误
  14. 一般来说系统默认变量写为大写,而用户设定的使用小写。
  15. 在变量后追加东西:
  16. # 在 PATH 这个变量当中『累加』:/home/dmtsai/bin 这个目录
  17. $ PATH=$PATH:/home/dmtsai/bin
  18. $ PATH="$PATH":/home/dmtsai/bin
  19. $ PATH=${PATH}:/home/dmtsai/bin
  20. # 上面这三种格式在 PATH 里头的设定都是 OK 的!但是底下的例子就不见得啰!
  21. # 我要将 name 的内容多出 "yes" 呢?
  22. [dmtsai@study ~]$ name=$nameyes
  23. # 知道了吧?如果没有双引号,那么变量成了啥?name 的内容是 $nameyes 这个变量!
  24. # 呵呵!我们可没有设定过 nameyes 这个变量吶!所以,应该是底下这样才对!
  25. [dmtsai@study ~]$ name="$name"yes
  26. [dmtsai@study ~]$ name=${name}yes <==以此例较佳!
  27. 设置环境变量 export,取消变量使用的是 unset。在一般的状态下,父shell 的自定义变量是无法在子shell 内使用的。但是透过 export 将变量变成环境变量后,就能够在子shell 底下应用了
  28. 数组类型:直接 var[index]=content

我们通常有些目录是经常使用的,但不过每次一层一层的 cd 实在是太麻烦了。下面是一个非常实用的简化目录的方法。

➜  frp_0.21.0_darwin_amd64 export frpc=$(pwd)
➜  frp_0.21.0_darwin_amd64 frpc
zsh: command not found: frpc
➜  frp_0.21.0_darwin_amd64 $frpc
➜  frp_0.21.0_darwin_amd64 echo $frpc
/Users/bonky/OneDrive/附件/运维/外网访问服务器/frp_0.21.0_darwin_amd64


下次进入直接 cd $frpc 即可~

几个比较重要的环境变量

PS1 命令提示字符

PS1 影响的就是下面这个东西:

屏幕快照 2020-01-13 下午2.28.05

检查 PS1 的值如下:

[root@VM_0_3_centos ~]# echo $PS1
[\u@\h \W]\$


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 时,提示字符为 # ,否则就是 $ 啰~


然后我们只要修改 PS1 就可以修改提示符(强行魔改):

[root@VM_0_3_centos ~]# export PS1="[(\T) \W] ➜  "
[(02:39:04) ~] ➜  cd /


$RANDOM

$RANDOM 这个变量,可以随机生成 0~32767之间的整数数字。

$ 和 ?

echo $$ 的结果是自己当前 shell 的 PID,所以有种很骚的方法退出 shell:

[(02:42:41) ~] ➜  kill -9 $$

连接断开


echo $? 的结果是上一条指令是否成功执行,如果成功执行为0,否则为返回错误代码,可以用这个来进行指令执行错误的判断。

OSTYPE, HOSTTYPE, MACHTYPE

$ echo $OSTYPE
linux-gnu
$ echo $HOSTTYPE
x86_64
$ echo $MACHTYPE
x86_64-redhat-linux-gnu


多语言

语言变量主要包括两个 LANG 及 LC_ALL,如果我们需要临时修改,请使用下面的方式:

$ LANG=en_US.utf8
$ export LC_ALL=en_US.utf8


注意 LC_ALL 需要 export 才能生效。使用 locale 可以检查当前使用的语言。如果需要长期修改,只要把上面两句写到配置文件或者修改 /etc/locale.conf 即可。

几个有用的函数

read 读取变量

$ read [-pt] variable
选项与参数:
-p :后面可以接提示字符
-t :后面可以接等待的秒数


下面是一个示例:

$ read -p "yourname: " myname
yourname: zhuziquan
$ echo $myname
zhuziquan


declare/typeset 宣告变量类型

declare/typeset 的功能都是宣告变量的类型。(shell 默认全部为字符串)

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


下面是一个不定义变量类型导致计算结果为表达式的字符串的例子:

➜  ~ s=1+2
➜  ~ echo $s
1+2


我们可以把变量定义为数字:

➜  ~ declare -i a=1+2
➜  ~ echo $a
3


PS:shell 中只支持到取到整数,所以计算结果都为整数(没有小数部分)

ulimit 限制资源

操作系统中,有些资源是有限制的,通常每个用户所用的资源都有上限的,这个往往是由 ulimit 所配置的。

$ 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 可以看到当前系统所设置的限制:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7267
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 100001
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) 7267
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited


可以看出,每个用户所创建单一文件的大小,能够打开文件的个数都是有限制的(尤其是打开文件个数非常重要,因为在服务器端可能有同时访问多个文件的需求,如果超过限额则无法访问)

变量内容的修改

image-20200113160850215

删除

$ echo ${path}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

# 删除后缀为 local/bin 的
$ echo ${path#/*local/bin:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin


看看这个 ${path#/*local/bin:}path 是原变量,# 代表从头开始删除(非贪婪匹配),后面是一个正则表达式 /*local/bin:。如果像要贪婪匹配的话应该使用 ##

同样地,如果我们想要从末尾开始删除的话我们应该使用 % 或者 %%,替换掉 # 或者 ## 即可。

取代

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

$ echo ${path//sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 如果是两条斜线,那么就变成所有符合的内容都会被取代喔!


变量为空或空字符串的处理

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


所有的规则如下:

image-20200113173648078

可以发现,关于 str 有两种表达 strstr: 分别表示是否考虑 str 为空的情况,str 表示不考虑 str 为空字符串的情况,而 str: 表示考虑为空字符串的情况。

然后满足以上条件的话有四种操作:-+=?

  • -:代表设定为后面所接的字符串 exp,不满足设定为 str。
  • +:表示设定为空字符串,不满足则设定为 expr。
  • =:满足的话str,var全部设定为 expr。
  • ?:满足的话输出到 stderr(即报错)。

机制

指令执行顺序

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

修改登录欢迎信息

直接修改文件 /etc/motd 中的内容即可。

image-20200113181225539

同样如果想要修改欢迎界面(就是还没登录前那个 tty1~tty6),直接修改 /etc/issue 文件即可。由于我使用的是 ssh 远程登录服务器,无法看到。

$ cat /etc/issue
\S
Kernel \r on an \m


image-20200113181534806

配置文件读取

image-20200113183753134

login 与 non-login shell

  • 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 。

login 与 non-login shell 读取的配置文件数据并不一致,login shell 一般会读 /etc/profile~/.bash_profile,而 non-login shell 会读 ~/.bashrc

/etc/profile

/etc/profile 是多用户共享的整体的配置文件。这个 /etc/profile 主要会设置以下变量:

PATH:会依据 UID 决定 PATH 变量要不要含有 sbin 的系统指令目录;
MAIL:依据账号设定好使用者的 mailbox 到 /var/spool/mail/账号名;
USER:根据用户的账号设定此一变量内容;
HOSTNAME:依据主机的 hostname 指令决定此一变量内容;
HISTSIZE:历史命令记录笔数。CentOS 7.x 设定为 1000 ;
umask:包括 root 默认为 022 而一般用户为 002 等!(755和775)


然后他还会调用外部数据:

  • /etc/profile.d/*.sh:主要是一些命令的别名什么的(可能还会运行一些程序),比如我得下面就有这些东西。
$ ls /etc/profile.d/*.sh
/etc/profile.d/256term.sh                    /etc/profile.d/colorgrep.sh  /etc/profile.d/less.sh
/etc/profile.d/abrt-console-notification.sh  /etc/profile.d/colorls.sh    /etc/profile.d/vim.sh
/etc/profile.d/bash_completion.sh            /etc/profile.d/lang.sh       /etc/profile.d/which2.sh

  • /etc/locale.conf:由 /etc/profile.d/lang.sh 调入,负责多语言
  • /usr/share/bash-completion/completions/*:由 /etc/profile.d/bash_completion.sh 调入, 负责文件指令的补全等等

~/.bash_profile

一般可能有三种针对与用户的配置文件,安装顺序读取(只需要一个):

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

~/.bashrc

注意一点,由于 /etc/profile 与 ~/.bash_profile 都是在取得 login shell 的时候才会读取的配置文件,如果我们想要不注销读取配置文件,那么我们需要用到 source 指令或者点(.)指令:

$ source ~/.bashrc  <==底下这两个指令是一样的!
$   .   ~/.bashrc


其它配置文件

  • /etc/man_db.conf:指明 man 的目录。
  • ~/.bash_logout:记录了注销 bash 后系统要做的操作
  • ~/.bash_history

按键设定

$ stty      # 设定按键

$ stty -a   # 查看所有的按键
speed 38400 baud; rows 22; columns 114; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q;
stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
......

其中:

intr : 送出一个 interrupt (中断) 的讯号给目前正在 run 的程序 (就是终止啰!);
quit : 送出一个 quit 的讯号给目前正在 run 的程序;
kill : 删除在目前指令列上的所有文字;
eof : End of file 的意思,代表『结束输入』。
start : 在某个程序停止后,重新启动他的 output 
stop : 停止目前屏幕的输出;   (可以拿 find / 试下)
susp : 送出一个 terminal stop 的讯号给正在 run 的程序。


基本 bash 设定

使用 set 命令对 bash 进行设定:

[dmtsai@study ~]$ set [-uvCHhmBx]
选项与参数:
-u :预设不启用。若启用后,当使用未设定变量时,会显示错误讯息;
-v :预设不启用。若启用后,在讯息被输出前,会先显示讯息的原始内容;
-x :预设不启用。若启用后,在指令被执行前,会显示指令内容(前面有 ++ 符号)
-C :预设不启用。若使用 > 等,则若文件存在时,该文件不会被覆盖。


检查效果如下:

[root@VM_0_3_centos ~]# set -x
++ history -a
++ printf '\033]0;%s@%s:%s\007' root VM_0_3_centos '~'
[root@VM_0_3_centos ~]# ls
+ ls --color=auto
test.tar.xz
++ history -a
++ printf '\033]0;%s@%s:%s\007' root VM_0_3_centos '~'


执行逻辑

逻辑 作用
cmd1 ; cmd2 连续执行两条指令
cmd1 && cmd2 cmd1错误执行,那么 cmd2不会执行
cmd1 || cmd2 cmd1正确执行,那么 cmd2不会执行

特殊符号

image-20200113190536801

通配符:

image-20200113190615345

数据流重定向

下图是指令的数据流:

image-20200113190803716

stdout 和 stderr

  • >:以覆盖的方法将『正确的数据』输出到指定的文件或装置上;
  • >>:以累加的方法将『正确的数据』输出到指定的文件或装置上;
  • 2> :以覆盖的方法将『错误的数据』输出到指定的文件或装置上;
  • 2>>:以累加的方法将『错误的数据』输出到指定的文件或装置上;

这里覆盖和累加应该这么理解:加入我重定向到一个文件,并执行过一次指令后,> 会覆盖前面执行的文件,>> 会在文件的尾部追加。

下面是一个简单的使用的例子:

$ find /home -name .bashrc > list_right 2> list_error


如果我们想忽略掉错误,请重定向到 /dev/null

$ find /home -name .bashrc 2> /dev/null


假如我们打算把错误和正确结果写到一个文件:

$ find /home -name .bashrc > list 2> list   <==错误 $ find /home -name .bashrc > list 2>&1      <==正确 $ find /home -name .bashrc &> list              <==正确


stdin

< 的作用是本需要由键盘输入的数据,改由文件内容来取代。

$ cat > catfile < ~/.bashrc


<< 的作用是指定结束输入的字符。

$ cat > catfile << "eof" > This is a test.
> OK now stop
> eof           <==输入这关键词,立刻就结束而不需要输入 [ctrl]+d


使用思路

  • 屏幕输出的信息很重要,而且我们需要将他存下来的时候
  • 后台执行中的程序,不希望他干扰屏幕正常的输出结果时
  • 不想要显示错误信息的时候
  • 错误讯息与正确讯息需要分别输出时
Share

You may also like...

发表评论