Linux Shell Script

理解 bashrc 和 profile

在一般的 linux 或者 unix 系统中, 都可以通过编辑 bashrc 和 profile 来设置用户的工作环境, 很多文章对于 profile 和 bashrc 也都有使用, 但究竟每个文件都有什么作用和该如何使用呢?

首先我们来看系统中的这些文件, 一般的系统可能会有。

num file
1 /etc/profile
2 /etc/bashrc
3 ~/.bashrc
4 ~/.profile

而如果系统是 ubuntu 或者 debian 的话, 就不会有 /etc/bashrc 而会有 /etc/bash.bashrc 文件.

以上这些就是常用 profile 和 bashrc 文件了. 要理解这些文件之前还需要了解 Shell, Shell 的 login(登入) 和 interactive(交互式) 模式.

Shell 的模式

Shell 的分类

系统的 shell 有很多种, 比如 bash, sh, zsh 之类的, 如果要查看某一个用户使用的是什么 shell 可以通过 finger [USERNAME] 命令来查看. 我们这里只说 shell 是 bash 的情况, 因为如果是 sh 或者其他 shell 显然不会运行 bashrc 的.

login shell 和 no-login shell

login shell

“login shell” 代表用户登入, 比如使用 su - 命令, 或者用 ssh 连接到某一个服务器上, 都会使用该用户默认 shell 启动 login shell 模式.

该模式下的 shell 会去自动执行 /etc/profile~/.profile 文件, 但不会执行任何的 bashrc 文件, 所以一般再 /etc/profile 或者 ~/.profile 里我们会手动去 source .bashrc 文件.

no-login shell

而 no-login shell 的情况是我们在终端下直接输入 bash 或者 bash -c “CMD” 来启动的 shell.

该模式下是不会自动去运行任何的 profile 文件.

interactive shell 和 non-interactive shell

interactive shell

interactive shell 是交互式shell, 顾名思义就是用来和用户交互的, 提供了命令提示符可以输入命令.

该模式下会存在一个叫 PS1 的环境变量, 如果还不是 login shell 的则会去 source /etc/bash.bashrc 和 ~/.bashrc 文件

non-interactive shell

non-interactive shell 则一般是通过 bash -c “CMD” 来执行的bash.

该模式下不会执行任何的 rc 文件, 不过还存在一种特殊情况这个我之后详细讲述

各种模式下 RC 文件的执行

ssh sudo su 或者 mac 下开启终端

ssh 登入和 su - 是典型的 interactive login shell, 所以会有 PS1 变量, 并且会执行

1
2
/etc/profile
~/.profile

在命令提示符状态下输入 bash 或者 ubuntu 默认设置下打开终端这样开启的是 interactive no-login shell, 所以会有 PS1 变量, 只会执行

1
2
/etc/bash.bashrc
~/.bashrc

bash -c cmd 或者 bash BASHFILE 命令执行的 shell

这些命令什么都不会执行, 也就是设置 PS1 变量, 不执行任何 RC 文件

ssh server [cmd]

这是最特殊的一种模式, 理论上应该既是 非交互 也是 非登入的, 但是实际上他不会设置 PS1, 但是还会执行

1
2
/etc/bash.bashrc
~/.bashrc

这里还有一点值得注意的是 /etc/bashrc 任何情况下都不会执行.

bashrc 和 profile 的区别

看了之前那么多种状态组合, 最关键的问题是, 究竟 bashrc 和 profile 有什么区别呢?

profile

其实看名字就能了解大概了, profile 是某个用户唯一的用来设置环境变量的地方, 因为用户可以有多个 shell 比如 bash, sh, zsh 之类的, 但像环境变量这种其实只需要在统一的一个地方初始化就可以了, 而这就是 profile.

bashrc

bashrc 也是看名字就知道, 是专门用来给 bash 做初始化的比如用来初始化 bash 的设置, bash 的代码补全, bash 的别名, bash 的颜色. 以此类推也就还会有 shrc, zshrc 这样的文件存在了, 只是 bash 太常用了而已.

期望的执行顺序

=> 代表 在文件内部 source, 换行的 => 代表自身执行结束以后在 source, 同一行表示先 source 在执行自身

普通 login shell

/etc/profile
=> /etc/bash.bashrc

~/.profile
=> ~/.bashrc => /etc/bashrc

终端种直接运行 bash

/etc/bash.bashrc
~/.bashrc => /etc/bashrc

bash -c “CMD”

什么都不执行

ssh server “CMD”

/etc/bash.bashrc => /etc/profile
~/.bashrc => | /etc/bashrc => /etc/profile
| ~/.profile

这里会有点小混乱, 因为既有 /etc/bash.bashrc 又有 /etc/bashrc, 其实是这样的 ubuntu 和 debian 有 /etc/bash.bashrc 文件但是没有 /etc/bashrc, 其他的系统基本都是只有 /etc/bashrc 没有 /etc/bash.bashrc.

原文

parameters

  • $# → the number of parameters(function or script)

  • $? → the return code

  • $1 → the first parameter

  • $0 → in function,all parameter; in script, this is the path of script

  • $$ → current process id

  • $* 表示所有参数组成的一个字符串,在上面的例子中,为param1 param2 param3。这些参数之间的间隔是IFS的首个字母即空格,IFS包括TAB,空格,换行等字符。

  • $@ 等同于”$1” “$2”… “$N”。

  • $varname == ${varname}

语句

if

注意事项

  1. if与[之间要有空格
  2. []与判断条件之间也必须有空格
  3. ]与;之间不能有空格

二. 对字符串的判断

  1. 当两个字符串相同时返回真
    1
    if [ str1=str2 ]; then fi
  2. 当两个字符串不相等时返回真
    1
    if [ str1!=str2 ]; then fi
  3. 当字符串的长度大于0时返回真(判断变量是否有值)
    1
    if [ -n str1 ]; then fi
  4. 当字符串的长度为0时返回真
    1
    if [ -z str1 ]; then fi

三. 对数字的判断

  1. int1 -eq int2 int1和int2相等
  2. int1 -ne int2 int1不相等int2
  3. int1 -gt int2 int1大于int2
  4. int1 -ge int2 int1大于等于int2
  5. int1 -lt int2 int1小于int2
  6. int1 -le int2 int1小于等于int2

四. 对文件属性的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. 文件可读为真
if [ -r file ]; then fi

# 2. 文件可写为真
if [ -w file ]; then fi

# 3. 文件可执行为真
if [ -x file ]; then fi

# 4. 文件存在且为正规文件
if [ -f file ]; then fi

# 5. 文件为目录时为真
if [ -d file ]; then fi

# 6. 文件存在且为字符设备文件
if [ -c file ]; then fi

# 7. 文件存在且为块设备文件
if [ -b file ]; then fi

# 8. 文件大小为非0为真,可以判断文件是否为空
if [ -s file ]; then fi

# 9. 如果文件存在为真
if [ -e file ]; then fi

五. 逻辑判断

  1. -a
  2. -o
  3. !

while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while [ condition ]
do
command1
command2
command3
done


#!/bin/bash
x=1
while [ $x -le 5 ]
do
echo "Welcome $x times"
x=$(( $x + 1 ))
done

while read line
do
echo $line
done < /tmp/a

for

1
2
3
for i in *.mp4; do
mv $i ${i#*-}
done

about string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
:~$ echo "abcd" | tr a-z A-Z        #lower case of word
ABCD
:~$ echo "aBcd" | tr A-Z a-z #uppper case of word
abcd

:~$ tmp="abcd"; echo ${#tmp} # get length of string
4

:~$ tmp="abcd"; echo ${tmp#*b} #return 自左边起第一个字符后的内容
cd
:~$ tmp="abacad"; echo ${tmp##*a} # return 自左边起最后一个匹配字符后的内容
d

:~$ tmp="abcbd"; echo ${tmp%b*} #return 自右边起第一个字符前的内容
abc
:~$ tmp="abcbd"; echo ${tmp%%b*} # return 自右边起最后一个匹配字符前的内容
a

:~$ expr substr "abcdefg" 2 2
bc
:~$ expr substr "abcdefg" 4 3 #从第四个起截取3个字符
def

:~$ echo "abcd abcd abcd" | sed -e 's/ /_/' #s <= substitude
abcd_abcd abcd
$ echo "abcd abcd abcd" | sed -e 's/ /_/g' #看到没有,简短两个命令就实现了最小匹配和最大匹配g <= global
abcd_abcd_abcd


# bash shell
:~$ a="abcd adbc adbc"; echo ${a/ /_}
abcd_adbc adbc
:~$ a="abcd adbc adbc"; echo ${a// /_}
abcd_adbc_adbc


:~$ cat /tmp/aa.xml
<?xml version="1.0" encoding="utf8"?>
<user>
<loginName>18133832968@189.cn</loginName>
<capacity>11015021395968</capacity>
<available>11012390070293</available>
<maxFilesize>4294967296</maxFilesize>
<mail189UsedSize>641359</mail189UsedSize>
<orderAmount>0</orderAmount>
<provinceCode>130000</provinceCode>
</user>
:~$ awk -F "[<>]" '/<[a-zA-Z0-9]+>/{print $2"="$3}' "/tmp/aa.xml"
user=
loginName=18133832968@189.cn
capacity=11015021395968
available=11012390070293
maxFilesize=4294967296
mail189UsedSize=641359
orderAmount=0
provinceCode=130000

output color message

Black 0;30 Dark Gray 1;30
Blue 0;34 Light Blue 1;34
Green 0;32 Light Green 1;32
Cyan 0;36 Light Cyan 1;36
Red 0;31 Light Red 1;31
Purple 0;35 Light Purple 1;35
Brown/Orange 0;33 Yellow 1;33
Light Gray 0;37 White 1;37

1
2
3
4
5
6
7
8
#!/bin/bash

reset="\033[0m"
red="\033[0;31m"
yellow="\033[1;33m"
green="\033[0;32m"
echo "${red}hello ${yellow}world!${reset}haha"
echo "${green}hello world!${reset}haha"

脚本的自动化交互输入

1.重定向

这个方法很简单,把需要输入的内容按每行写入到文档中,然后运行脚本

1
:~$ ./vpncmd < content

2.使用管道

1
:~$ echo -e "3\n" | ./vpncmd

3.expect

expect是专门用来交互自动化的工具,但它有可能不是随系统就安装好的,有时需要自己手工安装该命令

1
:~$ apt-get install expect
  • spawn [command]: 指定需要哪个命令需要自动化
  • expect [message]: 需要等待的消息
  • send [message]: 是要发送的命令。此message需要加上\r\n以表示回车
  • expect off: 指明命令交互结束

方式一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

echo "change the passwd of user which in pure-ftpd"

expect << EOF
spawn pure-pw passwd root
expect {
"Password:" { send "root\r"; exp_continue }
"Enter it again:" { send "root\r" }
}
expect eof
exit 0
EOF
echo "save the changes."
pure-pw mkdb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

echo "change the passwd of user which in pure-ftpd"

expect -c '
spawn pure-pw passwd root
expect {
"Password:" { send "root\r"; exp_continue }
"Enter it again:" { send "root\r" }
}
expect eof
exit 0
'
echo "save the changes."
pure-pw mkdb

方式二

1
2
3
4
5
6
7
8
9
10
11
:~$ echo '
#!/usr/bin/expect
spawn pure-pw passwd root
expect "Password:"
send "root\n"
expect "Enter it again:"
send "root\n"
spawn pure-pw mkdb
expect off
' > tmp.sh
:~$ expect ./tmp.sh

File

1
2
3
4
5
6
7
sed -i "s/abcd/aaaa/" /tmp/bb       # replace "abcd" with "aaaa" in file 

grep "abcd" /tmp/bb # show line which containt string "abcd"

while read line ; do # read every line in file
echo $line
done < /tmp/aa

重定位

  • 0 –标准输入
  • 1 –标准输出
  • 2 –标准错误信息输出
  • >& –将所有输入重定位
1
2
3
4
5
6
7
8
9
10
11
:~# ls 1>/dev/null
:~# ls >/dev/null

# run thunderbird in background mode, and no information write to terminator
:~# thunderbird 2>/dev/null &

# run firefox in background mode and no messages show in terminator
:~# firefox 1>/dev/null 2>/dev/null &

# 先将「标准输出」定位至/dev/null,再将「错误输出」定位至「标准输出」,因「标准输出」已经定位至/dev/null,所以「错误输出」重定位至/dev/null
:~# firefox >/dev/null 2>&1 &

Get Current User Name

You can use the variables $USER which are not Bash builtins. These are, however, set as environmental variables in one of the Bash startup files. You can use the id command to get the same information.

$USER - Current user name.

1
2
3
4
devin:~$ echo "$USER"
devin
devin:~$ sudo echo "$USER"
devin

id command - Current user name.

1
2
3
4
5
6
7
8
devin:~$ id -u -n
devin
devin:~$ sudo id -u -n
root
:~$ id -u
1000
:~$ sudo id -u
0

whoami command

1
2
3
4
5
6
devin:~$ whoami
devin
devin:~$ sudo whoami
root
devin: who am i
devin pts/1 2019-06-18 21:18

logname command

1
2
3
4
5
6
7
8
9
10
11
12
devin:~$ logname
devin
devin:~$ sudo logname
devin

devin:~$ sudo su -
root:~$ logname
devin
root:~$ echo "$USER"
root
root:~$ id -u -n
root