Shell脚本编程之Bash特性-IO重定向-变量

Bash的特性

一、什么是shell?

  人机交互接口大致可分为:GUI(图形用户界面)、CLI(命令行接口) 两种。

命令解释器或shell程序是一种机制,通过使用它们,每个交互用户都可以向操作系统发出命令;操作系统也是通过它们,将相应的结果直接返回到用户。shell的功能只是提供给用户操作内核的接口。用户成功的登录到计算机后(当用户开始登录过程时,一个进程将被分配给用户),操作系统使用户进程去执行shell。

shell文本接收器可以接收用户键入的命令,文本接收器首先会对键入的字符串进行分析解释,识别出一些特殊符号并做相应处理。注意它只能识别通配符,而不能识别正则表达式。

操作系统通常没有内置的窗口界面,而是一个简单的面向字符的界面,用户输入一串字符(以“回车键”结束);并且操作系统,也输出一行行字符到屏幕上作为相应结果。用户登录进程执行shell,首先shell分析命令行,然后根据环境变量PATH的设置(不会查找当前目录),查找系统文件目录,找到一个文件名字或者是一个文件的完全路径名,当找到文件后,根据其他参数列表,执行该文件。

二、常见的shell

Shell Name A Bit of History
sh(Bourne)   The original shell from early versions ofUNIX.
csh,tcsh, zsh

The C shell, and its derivatives, originally created by Bill Joy of Berkeley UNIX fame. The C shell isprobably the third most popular type of shell after bash and the Korn shell.

ksh, pdksh

The Korn shell and its public domaincousin. Written by David Korn, this is the default shell on many commercialUNIX versions.

bash

The Linux staple shell from the GNU project. bash, or Bourne AgainSHell, has the advantage that thesource code is freely available, and even if it’s not currently running on yourUNIX system, it has probably been ported to it. bash has many similaritiesto the Korn shell.


如何查看当前系统支持的shell类型?

[zbj@localhost ~]$ cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
[zbj@localhost ~]$

三、历史命令机制(history)

  我们在命令行上键入的命令都会缓存至内存中,为了下次开机给用户保留命令历史记录,bash为每个用户家目录都创建一个 .bash_history 文件用于保存历史记录。 当用户正常退出终端时,才会把内存中命令记录缓存保存到 .bash_history文件中。

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

  • 当我们以 bash 登陆 Linux 主机之后,系统会主动的由家目录的 ~/.bash_history 读取以前曾经下过的命令,那么~/.bash_history 会记录几笔数据呢?这就与你 bash 的 HISTFILESIZE 这个变量配置值有关了

  • 假设我这次登陆主机后,共下达过 100 次命令,『等我注销时, 系统就会将 101~1100 这总共 1000 笔历史命令更新到 ~/.bash_history 当中。』 也就是说,历史命令在我注销时,会将最近缓存中的 HISTFILESIZE 笔记录到我的纪录文件当中啦!


3.1 history 相关的环境变量

  •  HISTFILE  用于指定保存历史命令记录的文件。bash启动的时候会读取~/.bash_history文件并载入到内存中,这个变量就用于设置.bash_history文件,bash退出时也会把内存中的历史回写到.bash_history文件。

        ~/.bash_history记录的是前一次登录所执行过得命令,而至于这一次登录所执行的命令都被存放在内存中,当你成功登出系统后,这些命令历史才会被记录到.bash_history中。

  • HISTFILESIZE  定义了在文件 ~/.bash_history 中保存命令的记录总数

  • HISTSIZE          定义了 history 命令输出的记录数。

  • HISTCONTROL     控制历史记录保留的方式

    • ignorespace 以空白字符开头的命令不记录

    • ignoredups  忽略重复,连续重复的命令只记录一次

    • ignoreboth  两者都生效(ignorespace + ignoredups)

  • HISTTIMEFORMAT  历史命令记录格式

使用HISTTIMEFORMAT显示时间戳

$ export HISTTIMEFORMAT='%F %T '
$ history | more

3.2 相关命令

    bash的history命令管理功能,使得history命令可以回顾,修改和重用之前使用过的历史命令。 

## 显示历史命令
# history      显示全部历史
# history N    显示之前执行过的若干命令,例:history 2 显示最近执行过的两条命令

## 清空history缓存
# history -c

## 写history
# history -w 让bash将历史命令立即从内存写到.bash_history文件
# history -a 将目前新增的 history 历史命令写入.bash_history文件

3.3 事件指示器(event designators)

    shell 中!叫做事件提示符,英文是:Event Designators,事件指示器 (event designator) 是一个对历史列表中某个命令行条目的引用。

!         开始一个历史命令替换,除非后面紧跟的是空格,制表符,行结束符,”=”,”(” 。【当使用内建命令shopt开启了extglob的shell选项】。

!n     会引用history中的第n个命令,比如输入 !100,就是执行history列表中的第100条命令

    在命令行上,把感叹号”!”放在双引号里执行命令会出错(译者注:比如说:echo “hello!”). 因为感叹号被解释成了一个历史命令. 然而在一个脚本文件里,这么写则是正确的,因为在脚本文件里Bash的历史机制被禁用了。

!n      执行第n个命令
!!      执行上一个命令
!STRING 执行最近一次以STRING开头的命令

## 执行历史命令
# !!      运行上一条命令
# !88     运行第88条命令
# !ca     运行上一个包含ca的命令

## 搜索历史命令
使用ctrl+r搜索历史中的字符串,重复按ctrl+r可以在历史命令列表中不断的向前搜索包含字符串的命令,回车就会执行查找的命令

Shell脚本编程之Bash特性-IO重定向-变量

1.2 重度推荐

Ctrl-r 
有时候,如果你想重新输入以前输入过的某条命令怎么办? 我见过两种做法:

l   不停的按向上方向键,试图找出那条命令

l   输入history命令,然后找到那条命令,或者grep一把history命令的输出

其实, 你有更好的选择, 那就是按 C-r, 然后输入你想要的命令中含有的单词, 就会出现含有这个单词的命令, 如果它不是你想要的命令, 就继续按C-r, 知道出现你想要的命令为止.

有的时候你需要在执行一条历史命令之前编辑它.比如,你可以像下面那样搜索“httpd”,终端显示历史命令“service httpd stop”,选择它把“stop”改为“start”然后执行它

[: 在命令提示符下按 Ctrl+R , 将会显示提示符‖reverse-i-search]

 (reverse-i-search)`httpd`: service httpd stop

: 看到你想要的命令后按下左键或者右键,就可以在执行这条命令之前编辑它了 C-r效果

(reverse-i-search)`ls': ls a b c

Alt-. 
我经常见别人用mkdir long-long-long-name-dir, 再输入cd, 后面跟那个长的不能再长的目录名, 这时候我就会告诉他, 其实你输入完cd, 可以按Alt-.,就可以自动输入那个长的不能再长的目录名了. 其实, Alt-.的真正作用就是把上一条命令的最后一个参数输入到当前命令行非常非常之方便, 强烈推荐. 如果继续按Alt-.,会把上上条命令的最后一个参数拿过来. 同样, 如果你想把上一条命令第一个参数拿过来咋办呢? Alt-0 Alt-.,就是先输入Alt-0, 再输入Alt-.. 如果是上上条命令的第一个参数呢? 当然是Alt-0 Alt-. Alt-..

“UNIX系统中命令历史机制仅适用于以交互式访问Shell,不能在Shell脚本中使用”这句话如何理解?

就是说 command history 功能(那个按上键调出历史命令的功能),只能在 interactive 模式(交互模式)下使用。运行脚本时脚本缺省处于非交互模式,不能使用命令历史功能。

四、命令与文件名补全(TAB | 连续2次 TAB TAB)

命令行自动补齐(automatic command line completion)

Bash为linux用户默认提供了下面的标准补全命令。

  • 变量名补全(Variablename completion)

  • 用户名补全(Username completion)

  • 主机名补全(Hostname completion)

  • Path路径补全(Pathname completion)

  • 文件名补全(Filename completion)

命令包括:内部命令和外部命令。内部命令就是shell本身提供的,相当于一个函数的功能。外部命令是某个路径下的可执行文件。

       执行外部命令需要根据PATH环境变量中指定的路径进行搜索,按从左至右的顺序依次查找,最先找到的即可匹配,并停止查找过程。但是当执行过一次之后,就不会再到PATH路径下查找了,为了加快查找命令的速度,直接把命令路径保存在缓存中(hash表),提高查找效率。

[zbj@localhost ~]$ help hash                    # hash是bash内置命令
hash:hash [-lr] [-p pathname] [-dt] [name ...]
    Remember or display program locations.
    
[zbj@localhost ~]$ hash
hits	command
   1	/usr/bin/last
   5	/usr/bin/sort
   1	/usr/bin/rm
   1	/usr/bin/cat
   2	/usr/bin/vim

五、命令别名(alias)

# 设置别名
alias name='command [option] [argument]'

# 取消指定的别名设置
unalias name

# 列出设置的别名
alias

# 转义别名
alias rm='rm -i'
/rm        # 转义别名而使用原始的命令

[root@localhost ~]# rm tmp.txt
rm: remove regular file `tmp.txt'? n
[root@localhost ~]# /rm tmp.txt    # /rm,使用 / 对别名进行转义

问题思考

1. 怎么取消指定别名?

2. 别名在shell脚本中有效吗?

3. 怎样列出所有别名?

4. 怎样取消所有别名?

5. 怎样执行ls命令本身,而不是别名?

正常情况下,bash会检查指令的第一个token是不是别名,若是,则把别名替换成真正的指令。

  • 在shell中定义的别名,仅在当前shell生命周期中生效。通常我们通过命令配置都是在内存中临时生效的,如果要永久生效,需通过修改程序的配置文件来实现。

  • 别名可以嵌套,但是不会无限递归,造成死循环。

每个简单命令的第一个字token,如果被引用的话就进行是否有设置别名的检查。如果存在,这个token就被别名的文本替换。别名的名字和替换的文本可能包含任何错误的shell输入,包括shell特殊字符,例外的是别名的名字不可以包含 “=”。 替换的文本的第一个token进行别名的测试,但是对同一个别名已经扩展的token不进行第二次扩展。这样意味着 ls 可以成为 ls -F,而且Bash不会尝试递归扩展替换的文本。如果别名值的最后一个字符是一个空格或者制表符(TAB),那么跟随在别名之后的下一个命令会进行别名扩展的检查。

别名在需要把某个在你系统上存在多个版本的命令指定为默认版本的时候非常有用,或者为命令指定一个默认的选项。别名的另外一个用途是纠正不正确的拼写。

在shell不是交互的时候,别名不会进行扩展,除非expand_aliases 选项设置成用shopt shell内建命令。

六、通配符(wildcard, gblobbing

[root@localhost ~]# ls *.sh
init_use.sh  mysql_bak.sh

通配符是系统shell层次(level的),由shell负责解释, 而正则表达式需要相关工具的支持: egrep, awk, vi, perl。      

  • 在文本过滤工具里,都是用正则表达式,比如像awk,sed等,是针对文件的内容的。      

  • 通配符多用在文件名上,比如查找 find,ls,cp,等等。通配用于描述【文件名匹配】,而且必须完整的匹配整个文件名。作用非常单一,所以功能没有正则表达式牛X。

  通配符是由shell本身处理的(不是由所涉及到命令语句处理的), 它只会出现在命令的“参数”里(它不用在 命令名称里, 也不用在 操作符上)。当shell在“参数”中遇到了通配符时,shell会将其当作文件名去在磁盘上搜寻可能的匹配:若符合要求的匹配存在,则进行代换(路径扩展);否则就将该通配符作为一个普通字符传递给“命令”,然后再由命令进行处理。总之,通配符 实际上就是一种shell实现的路径扩展功能。在 通配符被处理后, shell会先完成该命令的重组,然后再继续处理重组后的命令,直至执行该命令。

  这种支持也叫做 “globbing”(由于历史原因),允许您通过使用通配符模式一次指定多个文件。Bash 和其它 Linux 命令将通过在磁盘上查找并找到任何与之匹配的文件来解释这种模式。

  我们回过头分析上面命令吧:*.sh 实际shell搜索文件,找到了符合条件的文件,命令会变成:ls init_use.sh mysql_bak.sh ,实际在执行ls 时候传给它的是init_use.sh mysql_bak.sh .

了解了shell通配符,我们现在看下,shell常见通配符有哪一些呢。

*    匹配任意个字符

?    匹配任意单个字符

[]    匹配指定范围内的任意单个字符

Shell脚本编程之Bash特性-IO重定向-变量

  • 总的来说,正是因为shell中的meta、wildcard有时会和command中的meta相同,为了让command中的meta不被shell解析以至于改变,就必须用shell quoting来保证其文字不变性

  • 通配符匹配文件名,但是不包含 ”.” 开头的(所以通配符无法匹配隐藏文件名)。


There are three quoting mechanisms: the escape character, single quotes, and double quotes.

Shell脚本编程之Bash特性-IO重定向-变量

六、快捷键(命令行编辑)

    在命令行输入命令的时候,我们可通过快捷键实现命令行编辑。

Ctrl +a :移到命令行首(a字母之首)

Ctrl +e :移到命令行尾(end)

Ctrl +u :从光标处删除至命令行首

Ctrl +k :从光标处删除至命令行尾

Ctrl +w : 删除一个单词(word)

Ctrl +l:清屏(clear)

Ctrl +c:终止命令 (SIGINT )

Ctrl +z:挂起命令

Ctrl +D:退出 OR  EOF(End Of File)

Ctrl +s:暂停终端传输

Ctrl +q:恢复终端传输

七、shell(scripts)

  命令的堆积。

八、基本的I/O重定向

     标准输入、输出:程序应该有数据的来源端、数据的目的端以及报告问题的地方。程序不必知道它的输入与输出背后是什么设备:磁盘上的文件、终端、网络连接或另一个程序。当程序启动时,可以预期的是,标准输出、输入都已打开,且已准备好供其使用。

     默认的情况下,它们会读取标准输入,写入标准输出,并将错误信息传递到标准错误输出,这类程序叫做过滤器。默认的标准输入、标准输出以及标准错误输出都是终端。

Shell脚本编程之Bash特性-IO重定向-变量

  那么是谁替执行的程序初始化标准输入、输出及错误输出的呢??  系统初始化。

  答案就是在你登录时,UNIX便将默认的标准输入、输出及错误输出安排成你的终端。I/O重定向就是你通过与终端交互,或是在shell脚本里设置,重新安排从哪里输入或输出到哪里。I/O重定向是Linux提供的一种多任务协调机制。

基本概念:

  1. I/O重定向通常与FD(File descriptor)有关,shellFD通常为10个,即09; 

  2. 常用FD3个,为0(stdin,STDIN_FILENO 标准输入)1(stdout,STDOUT_FILENO标准输出)、2(stderr,STDERR_FILENO标准错误输出),默认与keyboardmonitormonitor关联; 

  3. 用 来改变读进的数据信道(stdin),使之从指定的档案读进;是 的默认值,因此 与 0<是一样的;同理,与 1> 是一样的; 

  4. 用 来改变送出的数据信道(stdout, stderr),使之输出到指定的档案; 

  5. IO重定向 中,stdout 与 stderr 的管道会先准备好,才会从 stdin 读进资料; 

  6. 管道|(pipe line):上一个命令的 stdout(不包括stderr) 接到下一个命令的 stdin

  7. tee 命令是在不影响原本 I/O 的情况下,将 stdout 复制一份到档案去

  8. bashksh)执行命令的过程:分析命令-变量求值-命令替代($( ))-重定向-通配符展开-确定路径-执行命令; 

  9. ( ) 将 command group 置于 sub-shell 去执行,也称 nested sub-shell,它有一点非常重要的特性是:继承父shell的Standard input, output, and error plus any other open file descriptors。 

  10. exec 命令:常用来替代当前 shell 并重新启动一个 shell,换句话说,并没有启动子 shell。使用这一命令时任何现有环境都将会被清除。exec 在对文件描述符进行操作的时候,也只有在这时,exec 不会覆盖你当前的 shell 环境。

  11. 默认情况下,> 如果原有文件存在,默认操作是覆盖。如何禁止这种行为呢?

disallow existing regular files to be overwritten by redirection of output.
# set -C        # 关闭这个特性
# set +C        # 开启

如何把标准输出stdout, 标准错误输出stderr的数据通通写到同一个文件中呢?

find  /home -name .bashrc > file.out 2> file.out      # error ,可以达到效果,但是信息相互交错
find /home -name .bashrc &> file.out                  # right
find /home -name .bashrc > file.out 2>&1              # right

  … 2>&1 运行一个命令并把它的标准输出和输出合并。(严格的说是通过复制文件描述符来建立文件描述符,但效果通常是合并了两个流。

  我们对 2>&1详细说明一下:2>&1 也就是FD2FD1 ,这里并不是说FD2 的值等于FD1的值,因为是改变送出的数据信道,也就是说把FD2 的 数据输出通道” 改为FD1 的 数据输出通道。如果仅仅这样,这个改变好像没有什么作用,因为FD2 的默认输出和FD1的默认输出本来都是 monitor,一样的!但是,当FD1 是其他文件,甚至是其他FD 时,这个就具有特殊的用途了。请大家务必理解这一点。 

为何2>&1要写在后面?
      command > file 2>&1         #  使用 dup2()
      首先是command > file将标准输出重定向到file中, 2>&1 是标准错误拷贝了标准输出的行为,也就是同样被重定向到file中,最终结果就是标准输出和错误都被重定向到file中。 
      command 2>&1 >file 
      2>&1 标准错误拷贝了标准输出的行为,但此时标准输出还是在终端。>file 后输出才被重定向到file,但标准错误仍然保持在终端。

可以考虑一下不同的dup2()调用序列会产生怎样的文件共享结构。请参考APUE 3.10, 3.12

Shell脚本编程之Bash特性-IO重定向-变量


九、管道(pipe)

     program1 | program2 可将program1的标准输出修改为program2的标准输入。管道可以把多个执行中的程序衔接在一起(管道可以使的执行速度比使用临时文件的程序快上十倍)。构造管道时,应该试着让每个阶段的数据量变的更少,以提高性能。

  管道仅能处理由前面一个命令的标准输出(standard out

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/182684.html

(0)
上一篇 2021年11月1日
下一篇 2021年11月1日

相关推荐

发表回复

登录后才能评论