Linux基础:Bash特性

Bash的特性

一、历史命令机制(history)

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

1、相关变量

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

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

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

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

2、相关命令

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

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

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

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

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

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

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

## 清空历史命令
# history -c

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

使用HISTTIMEFORMAT显示时间戳

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

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

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


二、命令与文件名补全(TAB)

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

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

  • 变量名补全(Variablename completion)

  • 用户名补全(Username completion)

  • 主机名补全(Hostname completion)

  • Path路径补全(Pathname completion)

  • 文件名补全(Filename completion)


三、命令别名(alias)

# 设置别名
alias name='command line'

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

# 列出设置的别名
alias

# 转义别名
alias rm='rm -i'

[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环境生效,别名可以嵌套,但是不会无限递归。

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

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

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

四、shell(scripts)

  命令的堆积。


五、通配符(wildcard

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

  通配符是系统shell层次(level的), 而正则表达式需要相关工具的支持: egrep, awk, vi, perl。      
在文本过滤工具里,都是用正则表达式,比如像awk,sed等,是针对文件的内容的。      
通配符多用在文件名上,比如查找 find,ls,cp,等等。

  通配符是由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常见通配符有那一些了。

wKioL1UOXJaiJa8DAAJcx17R2EE434.jpg

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

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

wKioL1UOUF-BjvYmAAJeUKSJVl0055.jpg


六、基本的I/O重定向

  标准输入、输出:程序应该有数据的来源段、数据的目的端以及报告问题的地方。程序不必知道它的输入与输出背后是什么设备:磁盘上的文件、终端、网络连接或另一个程序。当程序启动时,可以预期的是,标准输出、输入都已打开,且已准备好供其使用。默认的情况下,它们会读取标准输入,写入标准输出,并将错误信息传递到标准错误输出,这类程序叫做过滤器。默认的标准输入、标准输出以及标准错误输出都是终端。

wKioL1UQOVCTLeiLAABd2V8XutA400.jpg

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

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

基本概念:

  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

wKiom1UQOlnhmS84AAD4h1Qv4eE637.jpg


七、管道(pipe)

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

  管道仅能处理由前面一个命令的标准输出(standard output),对于标准错误输出(stdandard error)没有直接处理能力。

  • 管道命令仅会处理standard output, 对于 standard error会予以忽略

  • 管道命令必须要能够接收来自前一个命令的数据成为standard input继续处理才行。


八、变量(variable)

变量命名规则:

1、只能包含字母、数字、下划线,并且首字母不能是数字(同大多数语言一致)
2、不应该和系统的环境变量重名,除非你知道你在做什么
3、环境变量全部大写,自定义变量全部小写,最好能见名知义。


变量赋值

var_name=value    # 等号两边不能有空格

a=375
hello=$a
#   ^ ^

#-------------------------------------------------------------------------
# No space permitted on either side of = sign when initializing variables.
# What happens if there is a space?

#  "VARIABLE =value"
#           ^
#% Script tries to run "VARIABLE" command with one argument, "=value".

#  "VARIABLE= value"
#            ^
#% Script tries to run "value" command with
#+ the environmental variable "VARIABLE" set to "".
#-------------------------------------------------------------------------

echo hello    # hello
# Not a variable reference, just the string "hello" ...

echo $hello   # 375
#    ^          This *is* a variable reference.
echo ${hello} # 375
#               Likewise a variable reference, as above.

# Quoting . . .
echo "$hello"    # 375
echo "${hello}"  # 375

echo

hello="A B  C   D"
echo $hello   # A B C D
echo "$hello" # A B  C   D
# As we see, echo $hello   and   echo "$hello"   give different results.
# =======================================
# Quoting a variable preserves whitespace.
# =======================================

echo

echo '$hello'  # $hello
#    ^      ^
#  Variable referencing disabled (escaped) by single quotes,
#+ which causes the "$" to be interpreted literally.

# Notice the effect of different types of quoting.


hello=    # Setting it to a null value.
echo "\$hello (null value) = $hello"      # $hello (null value) =
#  Note that setting a variable to a null value is not the same as
#+ unsetting it, although the end result is the same (see below).

# --------------------------------------------------------------

#  It is permissible to set multiple variables on the same line,
#+ if separated by white space.
#  Caution, this may reduce legibility, and may not be portable.

var1=21  var2=22  var3=$V3
echo
echo "var1=$var1   var2=$var2   var3=$var3"

# May cause problems with legacy versions of "sh" . . .

# --------------------------------------------------------------

# 数字
[root@skype ~]# num=1+2+3
[root@skype ~]# echo $num
1+2+3
[root@skype ~]# declare -i num=1+2+3
[root@skype ~]# echo $num
6


变量取消

unset name


引用变量(Variable Substitution)

Referencing (retrieving) its value is calledvariable substitution.

Enclosing a referenced value in double quotes (" ... ") does not interfere with variable substitution. This is calledpartial quoting, sometimes referred to as "weak quoting." Using single quotes (' ... ') causes the variable name to be used literally, and no substitution will take place. This is full quoting, sometimes referred to as 'strong quoting.'

Note that $variable is actually a simplified form of ${variable}.


如果变量在语句当中被引用,必须要使用${x}才可以,取得数组的变量值时候也需要使用${}来调用

wKiom1UQPxXwdGRjAAC6gboFuXc930.jpg


查看变量

  • 查看当前shell中的所有变量(all variables):

    set

  • 如果查看当前shell中的环境变量(envionment)
    env | export | printenv | declare -x

严格意义上的Bash变量类型 Bash Variables Are Untyped

  Unlike many other programming languages, Bash does not segregate its variables by "type." Essentially, Bash variables are character strings, but, depending on context, Bash permits arithmetic operations and comparisons on variables. The determining factor is whether the value of a variable contains only digits.

Special Variable Types

Local variables

  Variables visible only within a code block or funciton.

#!/bin/bash
# 在函数内部的全局和局部变量.

func ()
{
  local loc_var=23       # 声明为局部变量.
  echo                   # 使用内建的'local'关键字.
  echo "\"loc_var\" in function = $loc_var"
  global_var=999         # 没有声明为局部变量.
                         # 默认为全局变量.
  echo "\"global_var\" in function = $global_var"
 }

Environment variables

  Variables that affect the behavior of the shell and user interface.

In a more general context, each process has an "environment", that is, a group of variables that the process may reference. In this sense, the shell behaves like any other process.

  Every time a shell starts, it creates shell variables that correspond to its own environmental variables. Updating or adding new environmental variables causes the shell to update its environment, and all the shell's child processes (the commands it executes) inherit this environment.

 Child processes cannot export variables back to the parent processes that spawned them.


Positional parameters

  Arguments passed to the script from the command line [1] : $0$1$2$3 . . .

$0 is the name of the script itself, $1 is the first argument, $2 the second, $3 the third, and so forth.After $9, the arguments must be enclosed in brackets, for example, ${10}, ${11}, ${12}.

The special variables $* and $@ denote all the positional parameters.

wKioL1UQQsSA9srjAAMbR3Qi3wQ720.jpg


本地变量:作用域为整个bash进程

  varname=value
局部变量:作用域只对当前代码段有效,如函数
  local varname=value

环境变量:作用域为当前shell进程及其子进程,比本地变量的作用范围更广一些
  export varname=value  # "导出"

  declare -x varname=value

位置变量(Positional Parameters):
  $1,$2,.... ${10}, 由于历史原因,超过10以上的必须使用{},否则$10被识别为 {$1}0

特殊变量:保持某些特殊数据
  $? 上一个命令执行状态返回值
  $# 参数的个数
  $* 参数列表
  $@ 参数列表
 $0  命令本身  脚本本身执行脚本时的脚本路径及名称

  $$  “$” Expands to the process ID of the shell. In a () subshell, it  expands to the process ID of the current shell, not the sub�\shell.

数组和关联数组

Bash中可以使用两种容器。
一种是数组,另一种是关联数组,类似于其他语言中的Map/Hash/Dict。
声明数组的常用语法: declare -a ARY或者ARY=(1 2 3)
声明关联数组的唯一语法: declare -A MAP

赋值的语法:
直接ARY[N]=VALUE,N可以是数字索引也可以是键。关联数组可以使用MAP=([x]=a [y]=b)进行多项赋值,注意这是赋值的语句而不是声明。
亲测数组中的索引不一定要按顺序来,你可以先给2和3上的元素赋值。(同样算是弱类型的Javascript也支持这种无厘头赋值,这算通病么?)

往现有数组批量添加元素:

ARY+=(a b c)MAP+=([a]=1 [b]=2)

取值:

${ARY[INDEX]}${MAP[KEY]}

注意花括号的使用

${A[@]} 展开成所有的变量,而获取数组长度使用 ${#A[@]}

切片:
${ARY[@]:N:M} N是offset而M是length

返回索引,相当于keys():
${!MAP[@]}

试试下面的代码:

declare -a ARYdeclare -A MAP
MAP+=([a]=1 [b]=2)
ARY+=(a b c)echo ${ARY[1]}echo ${MAP[a]}echo "${ARY[@]}"echo "${MAP[@]}"echo "${ARY[@]:0:1}"echo ${#ARY[@]}echo "${!MAP[@]}"ARY[4]=aecho ${ARY[@]}echo ${ARY[3]}

移除匹配的字符串

%xx 从后往前,开始匹配,移除匹配的内容
%%xx 跟上面的差不多,不过这是贪婪匹配
#xx 从前往后,开始匹配,移除匹配的内容
##xx 跟上面的差不多,不过这是贪婪匹配

这个比较难理解,不过看下面几个例子应该能明白了。

FILENAME=/home/spacewander/param.shecho ${FILENAME%/*} # /home/spacewanderecho ${FILENAME%%/*} #echo ${FILENAME#*/} # home/spacewander/param.shecho ${FILENAME##*/} # param.sh

查找并替换

/MATCH/VALUE 替换第一个匹配的内容。
//MATCH/VALUE 替换匹配的内容

echo ${FILENAME/home/office} # /office/spacewander/param.shecho ${FILENAME//s/S} # /home/Spacewander/param.Sh

其它字符串操作

获取变量(字符串)长度:${#FILENAME}

字符串切片:跟数组切片是同样的语法,${STR:offset:len}

TEXT=这个程序充满了BUG!echo ${TEXT:0:8}echo ${TEXT:4}# 你还可以使用负数作为offset,这时候就是从后往前算起。注意负数要用括号括起来,避免冲突。echo ${TEXT:(-4)}

关于变量,其它的内容

Bash中有一项特性,你可以方便地检查某个变量是否设置了,如果没有设置,就赋予一个默认值。尤其在处理环境变量的时候,这项特性会让你感到欣慰。
语法是${VAR:=VALUE}或者${VAR:=VALUE}。此外,还有一个相似的语法,${VAR:=VALUE}${VAR:=VALUE}

下面展示下两者的区别

# expand to default variableecho ${NULL-"Not null"} # Not nullecho ${NULL} ## set default variableecho ${NIL="Not nil"} # Not nilecho ${NIL} # Not nil

可以看出,前者只是当变量不存在时,展开成指定的值。而后者在变量不存在时,将变量的值设置为指定值。

最后介绍一个,当目标变量不存在时,指定报错信息的语法。

echo ${TARGET?Not Found} # 当$TARGET不存在时,显示TARGET: Not Found,并结束程序。

Bash配置文件

 bash在启动的时候,会读取一些配置文件,以配置用户环境。要注意的是,命令别名、自定义变量等在你注销bash后就会失效,所以你要保留你的设置,就得把这些设置写入配置文件才行。

  有两种登录方式:login和nologin:关键在于有没有登录(login)

  • login shell:登录shell时需要完整的登录流程,称为 login shell。何为完整登录:需要输入用户名和密码。例如:走tty1-tty6控制终端,或走ssh等伪终端远程登入

  • non-login shell:登入shell时不需要输入帐号信息。例如在X11下,GNOME用terminal启动出来的shell是non-login shell。退出该non-login shell的话,只需要exit即可。或者在shell下,进入shell子进程,在bash环境下再次执行bash命令。当在已经存在的shell里面启动另外一个shell的时候,比如使用"bash"或者"su",启动的这个新shell就会初始化rc相关 的脚本。这个shell就称为non-login shell。

这两种登入shell方式的区别是:在登入shell时,读取的配置文件不同。这里先介绍两个配置文件/etc/profile和~/.bashrc,在unix系统中,这两个shell环境的配置文件,是我们接触最多的两个文件:

  • /etc/profile,处在shell配置文件的最顶端。这是系统shell环境的全局设定,例如PATH,MAIL很多环境变量。对它的修改,会影响到所有用户。

  • ~/.bashrc,处在shell配置文件的最低端。这是针对每个用户shell环境的配置文件,我们的大部分个性化的定制,都可以直接修改在这个文件中。

Profile:

      通常用于:设定环境变量、运行命令或脚本

Bashrc:

      通常用于:设定本地变量、设定命令别名


login shell(bash)在登入时,会读取的配置文件:

  • /etc/profile,全局配置

  • ~/.bash_profile 或~/.bash_login 或 ~/.profile,个人配置。之所以有三个文件,是因为不同的shell有可能命名不同,只会按顺序读取其中的一个。

  • /etc/profile.d/*.sh

wKiom1UOW7CQQ_m0AAC632kexmQ992.jpg

/etc/profile ->/etc/profile.d/* -> ~/.bash_profile -> ~/.bashrc -> /etc/bashrc


non-login shell(bash)在登入时,会读取的配置文件:

  • 只会读取的配置文件:~/.bashrc

~/.bashrc ->/etc/bashrc -> /etc/profile.d/*


su 与 su - 的区别?

su [-lc] [username]
-,-l,--login:表示使用以login shell的方式登录username,若username为空,则默认登录root。
       如果没有该参数,则以nonlogin的方式登录
-c, 仅执行一次命令 ,命令需要用引号括起来

这里要强调的就是su 和su - 的区别,就是前面扯了一大串的login 和non-login的区别

su username    # non-login shell

su - usernmae  # login shell


读入配置文件

由于/etc/profile等都是在登录后才会读取的配置文件,所以,如果你将自己的设置写入上述文件后,通常都是得注销后再登录,该配置才会生效。那么能不能直接读取配置文件而不注销登录呢??可以的,利用 source . 命令。重新读取配置文件,在当前shell生效。


你可能感兴趣的:(linux,history,英文,制表符,指示器)