您的位置:首页 > 运维架构 > Shell

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

2015-03-24 00:54 916 查看

Bash的特性

一、什么是shell?

人机交互接口大致可分为:GUI(图形用户界面)、CLI(命令行接口) 两种。
命令解释器或shell程序是一种机制,通过使用它们,每个交互用户都可以向操作系统发出命令;操作系统也是通过它们,将相应的结果直接返回到用户。shell的功能只是提供给用户操作内核的接口。用户成功的登录到计算机后(当用户开始登录过程时,一个进程将被分配给用户),操作系统使用户进程去执行shell。shell文本接收器可以接收用户键入的命令,文本接收器首先会对键入的字符串进行分析解释,识别出一些特殊符号并做相应处理。注意它只能识别通配符,而不能识别正则表达式。操作系统通常没有内置的窗口界面,而是一个简单的面向字符的界面,用户输入一串字符(以“回车键”结束);并且操作系统,也输出一行行字符到屏幕上作为相应结果。用户登录进程执行shell,首先shell分析命令行,然后根据环境变量PATH的设置(不会查找当前目录),查找系统文件目录,找到一个文件名字或者是一个文件的完全路径名,当找到文件后,根据其他参数列表,执行该文件。

二、常见的shell

Shell NameA Bit of History
sh(Bourne) The original shell from early versions ofUNIX.
csh,tcsh, zshThe 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, pdkshThe Korn shell and its public domaincousin. Written by David Korn, this is the default shell on many commercialUNIX versions.
bashThe 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可以在历史命令列表中不断的向前搜索包含字符串的命令,回车就会执行查找的命令



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中的meta、wildcard有时会和command中的meta相同,为了让command中的meta不被shell解析以至于改变,就必须用shell quoting来保证其文字不变性。
通配符匹配文件名,但是不包含 ”.” 开头的(所以通配符无法匹配隐藏文件名)。

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



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

在命令行输入命令的时候,我们可通过快捷键实现命令行编辑。
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重定向

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



那么是谁替执行的程序初始化标准输入、输出及错误输出的呢?? 系统初始化。
答案就是在你登录时,UNIX便将默认的标准输入、输出及错误输出安排成你的终端。I/O重定向就是你通过与终端交互,或是在shell脚本里设置,重新安排从哪里输入或输出到哪里。I/O重定向是Linux提供的一种多任务协调机制。
基本概念:
I/O重定向通常与FD(File descriptor)有关,shell的FD通常为10个,即0~9;
常用FD有3个,为0(stdin,STDIN_FILENO 标准输入)、1(stdout,STDOUT_FILENO标准输出)、2(stderr,STDERR_FILENO标准错误输出),默认与keyboard、monitor、monitor关联;
用 < 来改变读进的数据信道(stdin),使之从指定的档案读进;0 是 < 的默认值,因此 < 与 0<是一样的;同理,> 与 1> 是一样的;
用 > 来改变送出的数据信道(stdout, stderr),使之输出到指定的档案;
在IO重定向 中,stdout 与 stderr 的管道会先准备好,才会从 stdin 读进资料;
管道“|”(pipe line):上一个命令的 stdout(不包括stderr) 接到下一个命令的 stdin;
tee 命令是在不影响原本 I/O 的情况下,将 stdout 复制一份到档案去;
bash(ksh)执行命令的过程:分析命令-变量求值-命令替代(``和$( ))-重定向-通配符展开-确定路径-执行命令;
( ) 将 command group 置于 sub-shell 去执行,也称 nested sub-shell,它有一点非常重要的特性是:继承父shell的Standard input, output, and error plus any other open file descriptors
exec 命令:常用来替代当前 shell 并重新启动一个 shell,换句话说,并没有启动子 shell。使用这一命令时任何现有环境都将会被清除。exec 在对文件描述符进行操作的时候,也只有在这时,exec 不会覆盖你当前的 shell 环境。
默认情况下,> 如果原有文件存在,默认操作是覆盖。如何禁止这种行为呢?

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 运行一个命令并把它的标准输出和输出合并。(严格的说是通过复制文件描述符1 来建立文件描述符2 ,但效果通常是合并了两个流。) 我们对 2>&1详细说明一下:2>&1 也就是FD2=FD1 ,这里并不是说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


九、管道(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}才可以,取得数组的变量值时候也需要使用${}来调用




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

如果查看当前shell中的环境变量(environment)
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.

由于在默认的情况下, bash 对于变量有几个基本的定义:
变量类型默认为『字符串』,使用其他类型首先要声明
bash 环境中的数值运算,默认最多仅能到达整数形态,所以 1/3 结果是 0;

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.



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

varname=value
局部变量:作用域只对当前代码段有效,如函数
local varname=value
环境变量:作用域为当前shell进程及其子进程,比本地变量的作用范围更广一些
ENV_VAR=value
export ENV_VAR # 先声明,在导出
export varname=value # "导出"
declare -x varname=value

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

特殊变量:保持某些特殊数据
$? 上一个命令执行状态返回值
$# 参数的个数,提供传递到shell脚本或函数的参数总数。
$* 参数列表,只有"$*" 与 "$@" 用双引号引起来时,两者才会不同
$@ 参数列表
"$*" 将所有命令行参数视为单个字符串。等同于"$1 $2 ..."
"$@" 将所有命令行参数视为单独的个体。等同于"$1" "$2" ...
$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
=VALUE
,N可以是数字索引也可以是键。关联数组可以使用
MAP=([x]=a [y]=b)
进行多项赋值,注意这是赋值的语句而不是声明。
亲测数组中的索引不一定要按顺序来,你可以先给2和3上的元素赋值。(同样算是弱类型的Javascript也支持这种无厘头赋值,这算通病么?)往现有数组批量添加元素:
ARY+=(a b c)
MAP+=([a]=1 =2)
取值:
${ARY[INDEX]}${MAP[KEY]}
注意花括号的使用
${A[@]}
展开成所有的变量,而获取数组长度使用
${#A[@]}
切片:
${ARY[@]:N:M}
N是offset而M是length返回索引,相当于keys():
${!MAP[@]}
试试下面的代码:
declare -a ARY
declare -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]=a
echo ${ARY[@]}
echo ${ARY[3]}

移除匹配的字符串

%xx
从后往前,开始匹配,移除匹配的内容
%%xx
跟上面的差不多,不过这是贪婪匹配
#xx
从前往后,开始匹配,移除匹配的内容
##xx
跟上面的差不多,不过这是贪婪匹配这个比较难理解,不过看下面几个例子应该能明白了。
FILENAME=/home/spacewander/param.sh
echo ${FILENAME%/*}   # /home/spacewander
echo ${FILENAME%%/*}  #
echo ${FILENAME#*/}   # home/spacewander/param.sh
echo ${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 variable
echo ${NULL-"Not null"} # Not nullecho ${NULL}

## set default variable
echo ${NIL="Not nil"} # Not nilecho ${NIL} # Not nil
可以看出,前者只是当变量不存在时,展开成指定的值。而后者在变量不存在时,将变量的值设置为指定值。最后介绍一个,当目标变量不存在时,指定报错信息的语法。
echo ${TARGET?Not Found} # 当$TARGET不存在时,显示TARGET: Not Found,并结束程序。

Bash配置文件

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

如何实现在一个相同的大环境下,程序根据不同的用户,而有不同的设定??
Linux 配置文件通常是两段式:全局+ 局部对于一个程序来说:有一个全局配置文件(对所有用户生效),局部配置文件(对单个用户有效),首先会读取全局配置文件,然后再读取局部配置文件。一般来说,[b]作用范围越小,越晚被执行,优先级越高,也就是最终生效的配置。
环境变量一般指在操作系统中用来指定操作系统运行环境的一些参数。用户可以对自己的运行环境进行定制,其方法就是修改相应的系统环境变量。子进程可以继承父进程的环境变量,但是子进程不能影响父进程。

全局配置文件
/etc/profile
/etc/profile/* # 为了方便管理,将profile拆成多个小的配置文件
/etc/bashrc
局部配置文件
~/.bashrc
~/.bash_profile

有两种登录方式: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,系统全局配置(login 登录会读取)

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

/etc/profile.d/*.sh




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

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

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

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

通常情况下,如果修改了配置文件,要注销再登录后设置才会生效。source命令可以直接读取配置文件而不注销登录。如:source ~/.bash_profile (也可以用句点 . 代替source)

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生效。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  制表符 指示器
相关文章推荐