详解shell中source、sh、bash、./执行脚本的区别

1、source命令用法

source FileName

  
作用:在当前bash环境下读取并执行FileName中的命令。该filename文件可以无”执行权限”

注:该命令通常用命令“.”来替代。

如:source .bash_profile

. .bash_profile两者等效。

source(或点)命令通常用于重新执行刚修改的初始化文档。

source命令(从 C Shell 而来)是bash shell的内置命令。

点命令,就是个点符号,(从Bourne Shell而来)。

2、sh和bash命令用法

sh FileName
bash FileName

作用:在当前bash环境下读取并执行FileName中的命令。该filename文件可以无”执行权限”

注:两者在执行文件时的不同,是分别用自己的shell来跑文件。

sh使用“-n”选项进行shell脚本的语法检查,使用“-x”选项实现shell脚本逐条语句的跟踪,

可以巧妙地利用shell的内置变量增强“-x”选项的输出信息等。

3、./的命令用法

./FileName

作用:打开一个子shell来读取并执行FileName中命令。

注:运行一个shell脚本时会启动另一个命令解释器.

每个shell脚本有效地运行在父shell(parent shell)的一个子进程里.

这个父shell是指在一个控制终端或在一个xterm窗口中给你命令指示符的进程.

shell脚本也可以启动他自已的子进程.

这些子shell(即子进程)使脚本并行地,有效率地地同时运行脚本内的多个子任务.

shell的嵌入命令:

  • : 空,永远返回为true
  • . 从当前shell中执行操作
  • break 退出for、while、until或case语句
  • cd 改变到当前目录
  • continue 执行循环的下一步
  • echo 反馈信息到标准输出
  • eval 读取参数,执行结果命令
  • exec 执行命令,但不在当前shell
  • exit 退出当前shell
  • export 导出变量,使当前shell可利用它
  • pwd 显示当前目录
  • read 从标准输入读取一行文本
  • readonly 使变量只读
  • return 退出函数并带有返回值
  • set 控制各种参数到标准输出的显示
  • shift 命令行参数向左偏移一个
  • test 评估条件表达式
  • times 显示shell运行过程的用户和系统时间
  • trap 当捕获信号时运行指定命令
  • ulimit 显示或设置shell资源
  • umask 显示或设置缺省文件创建模式
  • unset 从shell内存中删除变量或函数
  • wait 等待直到子进程运行完毕

下面再看下 shell 脚本各种执行方式(source ./.sh, . ./.sh, ./*.sh)的区别

结论一: ./.sh的执行方式等价于sh ./.sh或者bash ./*.sh,此三种执行脚本的方式都是重新启动一个子shell,在子shell中执行此脚本。

结论二: .source ./.sh和 . ./.sh的执行方式是等价的,即两种执行方式都是在当前shell进程中执行此脚本,而不是重新启动一个shell 而在子shell进程中执行此脚本。

验证依据:没有被export导出的变量(即非环境变量)是不能被子shell继承的

验证结果:

[root@localhost ~]#name=dangxu    //定义一般变量 
[root@localhost ~]# echo ${name} 
dangxu 
[root@localhost ~]# cat test.sh   //验证脚本,实例化标题中的./*.sh 
#!/bin/sh 
echo ${name} 
[root@localhost ~]# ls -l test.sh  //验证脚本可执行 
-rwxr-xr-x 1 root root 23 Feb 6 11:09 test.sh 
[root@localhost ~]# ./test.sh    //以下三个命令证明了结论一 
[root@localhost ~]# sh ./test.sh 
[root@localhost ~]# bash ./test.sh 
[root@localhost ~]# . ./test.sh   //以下两个命令证明了结论二 
dangxu 
[root@localhost ~]# source ./test.sh 
dangxu 
[root@localhost ~]# 

总结

以上所述是小编给大家介绍的shell中source、sh、bash、./执行脚本的区别,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

LINUX的bash如何给shell脚本传参数

bash命令后边可以跟任意的参数,具体要如何操作?

执行“vi test.sh”创建一个新的shell脚本。

脚本test.sh的内容如下:

#!/bin/sh
name=$1
echo "the ${name} !"

给新建的test.sh的脚本赋可执行权限,命令为“chmod 755 test.sh”。执行可以看到结果.

如果想判断参数为空则中止执行,可以

if [ "$1" = "" ]; then
        echo -e "请提供参数."
        exit 1
fi

或:

if [ $# == 0 ];then
    echo "没有带参数";
else
       echo "带了$#个参数"
fi

或:

if [ "$1" ];then
    echo "带参数";
else
    echo "没有带参数 "
fi

上面的代码其实和使用if结构的-z参数是一样的,都是用于检测字符串是符不空值。因此也可以换成使用-z参数来判断。示例代码如下 :

if [ -z "$1" ];then
    echo "没有带参数";
else
    echo "带参数"
fi

详解:

“name=$1″中$1为系统提供的位置参数,$0代表程序的名称,[$1/$2/…]从1开始为传递的参数。

linux系统除了提供位置参数还提供内置参数,内置参数如下: 

$# ----传递给程序的总的参数数目  

$? ----上一个代码或者shell程序在shell中退出的情况,如果正常退出则返回0,反之为非0值。   

$* ----传递给程序的所有参数组成的字符串。   

$n ----表示第几个参数,$1 表示第一个参数,$2 表示第二个参数 ...   $0 ----当前程序的名称   

$@----以"参数1" "参数2" ... 形式保存所有参数   

$$ ----本程序的(进程ID号)PID   

$! ----上一个命令的PID

Linux系统下通过bash shell脚本实现倒计时的方法

本文主要讲述如何在linux系统下通过bash shell 脚本来实现在屏幕上输出倒计时的方法。

先来看看实现后的脚本,如下:

#!/bin/bash
 # script name: ctimer.sh
 # Author: osetc.com
 # --------------------------------------------------------

row=2
 col=2
 countdown() {
 msg="starting..."
 clear
 tput cup $row $col
 echo -n "$msg"
 l=${#msg}
 l=$(( l+$col ))
 for i in {30..1}
 do
     tput cup $row $l
     echo -n "$i"
     sleep 1
 done
 }
 countdown

首先我们定义了一个名为countdown的shell 函数,在函数里定义了一个msg变量用于在屏幕上显示倒计时信息,clear 命令用于清除屏幕上的历史输出信息,tput cut 命令用于设置屏幕输出信息的位置,最后通过for循环来实现倒计时,并更新输出信息的位置。

执行上面的脚本

$bash ctimer.sh
 Starting...30

bash启动时加载配置文件过程

当用户登录系统时,会加载各种bash配置文件,还会设置或清空一系列变量,有时还会执行一些自定义的命令。这些行为都算是启动bash时的过程。

另外,有些时候登录系统是可以交互的(如正常登录系统),有些时候是无交互的(如执行一个脚本),因此总的来说bash启动类型可分为交互式shell和非交互式shell。更细分一层,交互式shell还分为交互式的登录shell和交互式非登录shell,非交互的shell在某些时候可以在bash命令后带上”–login”或短选项”-l”,这时也算是登录式,即非交互的登录式shell。

一、判断是否交互式、是否登录式

判断是否为交互式shell有两种简单的方法:

方法一:判断变量”-“,如果值中含有字母”i”,表示交互式。

[root@xuexi ~]# echo $-
himBH

[root@xuexi ~]# vim a.sh
#!/bin/bash
echo $-

[root@xuexi ~]# bash a.sh
hB

方法二:判断变量PS1,如果值非空,则为交互式,否则为非交互式,因为非交互式会清空该变量。

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

判断是否为登录式的方法也很简单,只需执行”shopt login”即可。值为”on”表示为登录式,否则为非登录式。

[root@xuexi ~]# shopt login_shell  
login_shell     on
[root@xuexi ~]# bash

[root@xuexi ~]# shopt login_shell
login_shell     off

所以,要判断是交互式以及登录式的情况,可简单使用如下命令:

echo $PS1;shopt login_shell
或者
echo $-;shopt login_shell

二、几种常见的bash启动方式

1、正常登录(伪终端登录如ssh登录,或虚拟终端登录)时,为交互式登录shell。

[root@xuexi ~]# echo $PS1;shopt login_shell 
[u@h W]$
login_shell     on

2、su命令,不带”–login”时为交互式、非登录式shell,带有”–login”时,为交互式、登录式shell。

[root@xuexi ~]# su root

[root@xuexi ~]# echo $PS1;shopt login_shell 
[u@h W]$
login_shell     off
[root@xuexi ~]# su -
Last login: Sat Aug 19 13:24:11 CST 2017 on pts/0

[root@xuexi ~]# echo $PS1;shopt login_shell
[u@h W]$
login_shell     on

3、执行不带”–login”选项的bash命令时为交互式、非登录式shell。但指定”–login”时,为交互式、登录式shell。

[root@xuexi ~]# bash

[root@xuexi ~]# echo $PS1;shopt login_shell
[u@h W]$
login_shell     off
[root@xuexi ~]# bash -l

[root@xuexi ~]# echo $PS1;shopt login_shell
[u@h W]$
login_shell     on

4、使用命令组合(使用括号包围命令列表)以及命令替换进入子shell时,继承父shell的交互和登录属性。

[root@xuexi ~]# (echo $BASH_SUBSHELL;echo $PS1;shopt login_shell)
1
[u@h W]$
login_shell     on
[root@xuexi ~]# su

[root@xuexi ~]# (echo $BASH_SUBSHELL;echo $PS1;shopt login_shell)
1
[u@h W]$
login_shell     off

5、ssh执行远程命令,但不登录时,为非交互、非登录式。

[root@xuexi ~]# ssh localhost 'echo $PS1;shopt login_shell'

login_shell     off

6、执行shell脚本时,为非交互、非登录式shell。但指定了”–login”时,将为非交互、登录式shell。

例如,脚本内容如下:

[root@xuexi ~]# vim b.sh
#!/bin/bash
echo $PS1
shopt login_shell

不带”–login”选项时,为非交互、非登录式shell。

[root@xuexi ~]# bash b.sh

login_shell     off

带”–login”选项时,为非交互、登录式shell。

[root@xuexi ~]# bash -l b.sh

login_shell     on

7、在图形界面下打开终端时,为交互式、非登录式shell。

未分类

但可以设置为使用交互式、登录式shell。

未分类

三、加载bash环境配置文件

无论是否交互、是否登录,bash总要配置其运行环境。bash环境配置主要通过加载bash环境配置文件来完成。但是否交互、是否登录将会影响加载哪些配置文件,除了交互、登录属性,有些特殊的属性也会影响读取配置文件的方法。

bash环境配置文件主要有/etc/profile、~/.bash_profile、~/.bashrc、/etc/bashrc和/etc/profile.d/*.sh,为了测试各种情形读取哪些配置文件,先分别向这几个配置文件中写入几个echo语句,用以判断该配置文件是否在启动bash时被读取加载了。

echo "echo '/etc/profile goes'" >>/etc/_profile
echo "echo '~/.bash_profile goes'" >>~/.bash_profile
echo "echo '~/.bashrc goes'" >>~/.bashrc
echo "echo '/etc/bashrc goes'" >>/etc/bashrc
echo "echo '/etc/profile.d/test.sh goes'" >>/etc/profile.d/test.sh
chmod +x /etc/profile.d/test.sh

1、交互式登录shell或非交互式但带有”–login”(或短选项”-l”,例如在shell脚本中指定”#!/bin/bash -l”时)的bash启动时,将先读取/etc/profile,再依次搜索~/.bash_profile、~/.bash_login和~/.profile,并仅加载第一个搜索到且可读的文件。当退出时,将执行~/.bash_logout中的命令。

但要注意,在/etc/profile中有一条加载/etc/profile.d/*.sh的语句,它会使用source加载/etc/profile.d/下所有可执行的sh后缀的脚本。

[root@xuexi ~]# grep -A 8 *.sh /etc/profile  
for i in /etc/profile.d/*.sh ; do
    if [ -r "$i" ]; then
        if [ "${-#*i}" != "$-" ]; then
            . "$i"
        else
            . "$i" >/dev/null 2>&1
        fi
    fi
done

内层if语句中的【”${-#i}” != “$-“】表示将”$-“从左向右模式匹配”i”并将匹配到的内容删除(即进行变量切分),如果”$-“切分后的值不等于”$-“,则意味着是交互式shell,于是怎样怎样,否则怎样怎样。

同样的,在~/.bash_profile中也一样有加载~/.bashrc的命令。

[root@xuexi ~]# grep -A 1 ~/.bashrc ~/.bash_profile
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

而~/.bashrc中又有加载/etc/bashrc的命令。

[root@xuexi ~]# grep -A 1 /etc/bashrc ~/.bashrc
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

其实/etc/bashrc中还有加载/etc/profile.d/*.sh的语句,但前提是非登录式shell时才会执行。以下是部分语句:

if ! shopt -q login_shell ; then   # We're not a login shell
...
    for i in /etc/profile.d/*.sh; do
        if [ -r "$i" ]; then
            if [ "$PS1" ]; then
                . "$i"
            else
                . "$i" >/dev/null 2>&1
            fi
        fi
    done
...
fi

从内层if语句和/etc/profile中对应的判断语句的作用是一致的,只不过判断方式不同,写法不同。

因此,交互式的登录shell加载bash环境配置文件的实际过程如下图:

未分类

以下结果验证了结论:

Last login: Mon Aug 14 04:49:29 2017     # 新开终端登录时
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
 [root@xuexi ~]# ssh localhost        # ssh远程登录时
root@localhost's password:
Last login: Mon Aug 14 05:05:50 2017 from 172.16.10.1
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
[root@xuexi ~]# bash -l        # 执行带有"--login"选项的login时
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
[root@xuexi ~]# su -          # su带上"--login"时
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
[root@xuexi ~]# vim a.sh    # 执行shell脚本时带有"--login"时
#!/bin/bash -l
echo haha

[root@xuexi ~]# ./a.sh 
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
haha

之所以执行shell脚本时没有显示执行/etc/profile.d/.sh,是因为它是非交互式的,根据/etc/profile中的【if [ “${-#i}” != “$-” ]】判断,它将会把/etc/profile.d/*.sh的执行结果重定向到/dev/null中。也就是说,即使是shell脚本(带”–login “选项),它也加载了所有bash环境配置文件。

2、交互式非登录shell的bash启动时,将读取~/.bashrc,不会读取/etc/profile和~/.bash_profile、~/.bash_login和~/.profile。

因此,交互式非登录shell加载bash环境配置文件的实际过程为下图内方框中所示:

未分类

例如,执行不带”–login”的bash命令或su命令时。

[root@xuexi ~]# bash
/etc/profile.d/*.sh goes
/etc/bashrc goes
~/.bashrc goes
[root@xuexi ~]# su
/etc/profile.d/*.sh goes
/etc/bashrc goes
~/.bashrc goes

3、非交互式、非登录式shell启动bash时,不会加载前面所说的任何bash环境配置文件,但会搜索变量BASH_ENV,如果搜索到了,则加载其所指定的文件。但有并非所有非交互式、非登录式shell启动时都会如此,见情况4。

它就像是这样的语句:

if [ -n "$BASH_ENV" ];then
    . "$BASH_ENV"
fi

几乎执行所有的shell脚本都不会特意带上”–login”选项,因此shell脚本不会加载任何bash环境配置文件,除非手动配置了变量BASH_ENV。

4、远程shell方式启动的bash,它虽然属于非交互、非登录式,但会加载~/.bashrc,所以还会加载/etc/bashrc,由于是非登录式,所以最终还会加载/etc/profile.d/*.sh,只不过因为是非交互式而使得执行的结果全部重定向到了/dev/null中。

如果了解rsync,就知道它有一种远程shell连接方式。所谓的远程shell方式,是指通过网络的方式启动bash并将bash的标准输出关联起来,就像它连接了一个远程的shell守护进程一样。一般由sshd实现这样的连接方式,老版的rshd也一样支持。

事实也确实如此,使用ssh连接但不登录远程主机时(例如只为了执行远程命令),就是远程shell的方式,但它却是非交互、非登录式的shell。

[root@xuexi ~]# ssh localhost echo haha
root@localhost's password:
/etc/bashrc goes
~/.bashrc goes
haha

正如上文所说,它同样加载了/etc/profile.d/*.sh,只不过/etc/bashrc中的if判断语句【if [ “$PS1” ]; then】使得非交互式的shell要将执行结果重定向到/dev/null中。

centos7.1 LVS健康检查bash脚本

环境:centos7.1

简介:

当脚本检测到某个RS的http服务掉线时,在LVS中自动移除RS;当所有RS的http服务掉线时,在LVS中移除所有RS,并将LVS调度器上的http服务加入到LVS中,作为告警页面。

当脚本检测到某个RS的http服务活跃时,自动将其加入到LVS中,如果有调度器本身的存在LVS中,将其删除。

该脚本经本人测试,完全达到要求。

以下为脚本:

#!/bin/bash
#
rs=("152.168.1.12" "152.168.1.13")
vip="152.168.1.10"
port=80
logfile="/usr/local/scripts/lvs.log"
function check_alldown {
   #有一个rs主机能访问,就说明不是全部掉了
   #检查到一个rs主机存活就退出检查
   #如果全部rs不能访问,说明主机全掉了
   for www in `echo ${rs[*]}`
   do
      curl --connect-timeout 1 http://$www &> /dev/null
      if [ $? -eq 0 ]
      then
          echo 0 
          exit 0 
      fi      
   done
   echo 100 
}
function lvs_add {
   ipvsadm -a -t $vip:$port -r $1
   echo "add rs host:$1 to lvs"
}
function lvs_rm {
   ipvsadm -d -t $vip:$port -r $1
   echo "remove rs host:$1 to lvs"
}
function lvs_local {
   #如果全部rs主机掉线,并且lvs中没有127.0.0.1就添加它
   #如果可以访问一个rs主机,并且lvs中有127.0.0.1就删除它
   all_down=`check_alldown`
   rip=$(ipvsadm -L -n | gawk '/127.0.0.1/')
   if [ $all_down -eq 100 ]
   then
       if [ "$rip" = "" ] 
       then   
           echo "`date +%F:%H-%M-%S` all rs host is down!" >> $logfile
           lvs_add "127.0.0.1"
       fi
   else
       if [ $all_down -eq 0 ] && [ ! "$rip" = "" ]  
       then
           echo "`date +%F:%H-%M-%S` one rs host is up,remove local rs host!" >> $logfile
           lvs_rm "127.0.0.1"
       fi
   fi
}
function lvs_rs {
   #如果可以访问一个rs主机,并且lvs中没有它就添加它
   #如果不能访问一个rs主机,并且lvs中有它就删除它
   lvs_local
   for www in `echo ${rs[*]}`
   do
      rip=$(ipvsadm -L -n | gawk "/$www/")
      curl --connect-timeout 1 http://$www &> /dev/null
      if [ $? -eq 0 ]
      then
          if [ "$rip" = "" ]
          then
              echo "`date +%F:%H-%M-%S` rs host:$www is up!" >> $logfile
              lvs_add "$www"
          fi
      else
          if [ ! "$rip" = "" ]
          then
              echo "`date +%F:%H-%M-%S` rs host:$www is down!" >> $logfile
              lvs_rm "$www"
          fi
      fi
   done
}
function lvs_monitor {
   while true
   do
     echo "check lvs rs health!"
     lvs_rs
     sleep 1
   done
}
lvs_monitor

完毕。

Bash使用示例(5) – 条件表达式

文件类型测试

-e条件运算符用来测试一个文件是否存在(包括所有文件类型,目录等)

  1. if [[ -e $filename ]]; then
  2.   echo "$filename exists"
  3. fi

也可以测试指定类型的文件

  1. if [[ -f $filename ]]; then
  2.   echo "$filename is a regular file"
  3. elif [[ -d $filename ]]; then
  4.   echo "$filename is a directory"
  5. elif [[ -p $filename ]]; then
  6.   echo "$filename is a named pipe"
  7. elif [[ -S $filename ]]; then
  8.   echo "$filename is a named socket"
  9. elif [[ -b $filename ]]; then
  10.   echo "$filename is a block device"
  11. elif [[ -c $filename ]]; then
  12.   echo "$filename is a character device"
  13. fi
  14. if [[ -L $filename ]]; then
  15.   echo "$filename is a symbolic link (to any file type)"
  16. fi

对于软链接,使用-L测试时,当链接指向的目标文件不存在时会返回false

  1. if [[ -L $filename || -e $filename ]]; then
  2.   echo "$filename exists (but may be a broken symbolic link)"
  3. fi
  4.  
  5. if [[ -L $filename && ! -e $filename ]]; then
  6.   echo "$filename is a broken symbolic link"
  7. fi

字符串比较和匹配

在两个带引号的字符串之间比较时使用==操作符。!=操作符则是否则的比较

  1. if [[ "$string1" == "$string2" ]]; then
  2.   echo "$string1 and $string2 are identical"
  3. fi
  4. if [[ "$string1" == "$string2" ]]; then
  5.   echo "$string1 and $string2 are identical"
  6. fi

如果右则没有引号,那么它是以通配符的模式作比较。

  1. string1=’abc’
  2. pattern=’a*’
  3. if [[ "$string1" == $pattern ]]; then
  4.   # the test is true
  5.   echo "The string $string1 matches the pattern $pattern"
  6. fi
  7. if [[ "$string1" != "$pattern" ]]; then
  8.   echo "The string $string1 is not equal to the string $pattern"
  9. fi

操作符以字典顺序来比较字符串(它们没有小或等于或大于的说法)
可以测试空字符串

  1. if [[ -n $string ]]; then
  2.   echo "$string is non-empty"
  3. fi
  4. if [[ -z "${string// }" ]]; then
  5.   echo "$string is empty or contains only spaces"
  6. fi
  7. if [[ -z $string ]]; then
  8.   echo "$string is empty"
  9. fi

以上的-z检查可能表示$string没有被设置或者已经设置了但为空字符。为了区分空字符和未设置,使用:

  1. if [[ -n ${string+x} ]]; then
  2.     echo "$string is set, possibly to the empty string"
  3. fi
  4. if [[ -n ${string-x} ]]; then
  5.     echo "$string is either unset or set to a non-empty string"
  6. fi
  7. if [[ -z ${string+x} ]]; then
  8.     echo "$string is unset"
  9. fi
  10. if [[ -z ${string-x} ]]; then
  11.     echo "$string is set to an empty string"
  12. fi

其中x是任意的,以表格形式如下:

  1. +——-+——-+———–+
  2.             $string is: | unset | empty | non-empty |
  3. +———————–+——-+——-+———–+
  4. | [[ -z ${string} ]]    | true  | true  | false     |
  5. | [[ -z ${string+x} ]]  | true  | false | false     |
  6. | [[ -z ${string-x} ]]  | false | true  | false     |
  7. | [[ -n ${string} ]]    | false | false | true      |
  8. | [[ -n ${string+x} ]]  | false | true  | true      |
  9. | [[ -n ${string-x} ]]  | true  | false | true      |
  10. +———————–+——-+——-+———–+

文件权限测试

  1. if [[ -r $filename ]]; then
  2.   echo "$filename is a readable file"
  3. fi
  4. if [[ -w $filename ]]; then
  5.   echo "$filename is a writable file"
  6. fi
  7. if [[ -x $filename ]]; then
  8.   echo "$filename is an executable file"
  9. fi

Bash使用示例(4) – 重定向输出

重定向标准输出

> 重定向当前命令的标准输出(STDOUT)到一个文件或者一个描述符。
下面的例子把ls命令的输出存到file.txt文件

  1. ls >file.txt
  2. > file.txt ls

目标文件如果不存在就会被创建,或者文件被截断。
如果不指定,默认重定向描述符是标准输出或1。下面的命令等同于上面的例子:

  1. ls 1>file.txt

追加 vs 截断

截断 >
1.如果文件不存在则创建
2.截断(删除文件内容)
3.写入文件

  1. $ echo "first line" > /tmp/lines
  2. $ echo "second line" > /tmp/lines
  3.  
  4. $ cat /tmp/lines
  5. second line

追加 >>
1.如果文件不存在则创建
2.追加文件(在文件底部写)

  1. # Overwrite existing file
  2. $ echo "first line" > /tmp/lines
  3.  
  4. # Append a second line
  5. $ echo "second line" >> /tmp/lines
  6.  
  7. $ cat /tmp/lines
  8. first line
  9. second line

重定向标准输出和标准错误

文件描述符像0和1都是指针。我们更改的是文件描述符的指向。
>/dev/null意思是1指向/dev/null。
首先我们把1(STDOUT)指向/dev/null,然后2指向1(不管1指向什么)。

  1. echo_to_stdout_and_stderr >/dev/null 2>&1

可以更短点:
echo_to_stdout_and_stderr &> /dev/null

使用命名管道

有时候你想把一个程序的标准输出作为其它多个程序的标准输入,这时候就不能用标准管道了,不过你可以写入一个临时文件,如:

  1. touch tempFile.txt
  2. ls -l > tempFile.txt &
  3. grep ".log" < tempFile.txt

这个方法可以在大多数情况下有效,但谁都不知道tempFile.txt会被哪个程序删除或者修改里面的内容。这时候命名管道就可以用上场了。

  1. mkfifo myPipe
  2. ls -l > myPipe
  3. grep ".log" < myPipe

myPipe在技术上是一个文件,所以我们来用ls -l看下当前创建管道的目录

  1. mkdir pipeFolder
  2. cd pipeFolder
  3. mkfifo myPipe
  4. ls -l

输出为:
prw-r–r– 1 root root 0 Jul 25 11:20 myPipe
注意权限的第一个字符,显示是pipe,不是文件。
现在我们做了有意思的。
打开一个终端,在一个空目录创建管道:

  1. mkfifo myPipe

现在我们输入点东西到管道:

  1. echo "Hello from the other side" > myPipe

你会注意到这个命令被挂起了,让我们打开一个新的终端,输入:

  1. cat < myPipe

你会发现当”hello from the other side”输出后,终端1就完成了,终端2也一样。
现在我们反向运行程序,先执行cat < myPipe,然后再输入点东西到myPipe,它仍然按预期工作,因为一个程序会一起等待直到管道中被输入一些东西。
命名管道在终端间或程序间传递信息时会非常有用。

输出错误信息到标准错误

错误信息通常为了调度会包含在脚本里。简单的输出错误信息如下:

  1. cmd || echo ‘cmd failed’

可能会在简单的场景工作,但不是通常的做法。在这个例子中,错误信息会会污染脚本实际的输出。简单来说,错误信息应该输出到标准错误而不是标准输出,如:

  1. cmd || echo ‘cmd failed’ >/dev/stderr

其它例子:

  1. if cmd; then
  2.     echo ‘success’
  3. else
  4.     echo ‘cmd failed’ >/dev/stderr
  5. fi

可以封装成一个函数:

  1. err(){
  2.     echo "E: $*" >>/dev/stderr
  3. }
  4. err "My error message"

Bash使用示例(3) – 使用trap处理信号

清理临时文件

你可以使用trap命令来捕获信号;shell中的trap捕获信号等同于C语言或大多数其它语言中的signal或者sigaction。
trap最常用的场景之一是在预期退出和意外退出时清理临时文件。
遗憾的是没有多少shell脚本这样做。

  1. #!/bin/sh
  2.  
  3. # Make a cleanup function
  4. cleanup() {
  5.   rm –force — "${tmp}"
  6. }
  7.  
  8. # Trap the special "EXIT" group, which is always run when the shell exits.
  9. trap cleanup EXIT
  10.  
  11. # Create a temporary file
  12. tmp="$(mktemp -p /tmp tmpfileXXXXXXX)"
  13.  
  14. echo "Hello, world!" >> "${tmp}"
  15.  
  16. # No rm -f "$tmp" needed. The advantage of using EXIT is that it still works
  17. # even if there was an error or if you used exit.

捕获SIGINT或Ctrl+C信号

当有子shell时,trap会被重置,所以sleep继续作用于由^C发送的SIGINT信号,而父进程(即shell脚本)就不会了。所以下面的例子只是退出了sleep,没有直接退出shell脚本,而是继续往下执行。

  1. #!/bin/sh
  2.  
  3. # Run a command on signal 2 (SIGINT, which is what ^C sends)
  4. sigint() {
  5.     echo "Killed subshell!"
  6. }
  7. trap sigint INT
  8.  
  9. # Or use the no-op command for no output
  10. #trap : INT
  11.  
  12. # This will be killed on the first ^C
  13. echo "Sleeping…"
  14. sleep 500
  15.  
  16. echo "Sleeping…"
  17. sleep 500

通过些许更改可以允许你按两个^C才退出程序:

  1. last=0
  2. allow_quit() {
  3.     [ $(date +%s) -lt $(( $last + 1 )) ] && exit
  4.     echo "Press ^C twice in a row to quit"
  5.     last=$(date +%s)
  6. }
  7. trap allow_quit INT

统计维护退出任务

你有没有在退出时忘记添加trap来清理临时文件或者其它事情?
有没有设置了一个trap而导致取消了另一个?
下面的代码能让你非常容易地在一个地方就把所有需要在退出时做的工作都添加进去,而不是需要一大段的trap声明,这个很容易忘记的。

  1. # on_exit and add_on_exit
  2. # Usage:
  3. #   add_on_exit rm -f /tmp/foo
  4. #   add_on_exit echo "I am exiting"
  5. #   tempfile=$(mktemp)
  6. #   add_on_exit rm -f "$tempfile"
  7. # Based on http://www.linuxjournal.com/content/use-bash-trap-statement-cleanup-temporary-files
  8. function on_exit()
  9. {
  10.     for i in "${on_exit_items[@]}"
  11.     do
  12.         eval $i
  13.     done
  14. }
  15. function add_on_exit()
  16. {
  17.     local n=${#on_exit_items[*]}
  18.     on_exit_items[$n]="$*"
  19.     if [[ $n -eq 0 ]]; then
  20.         trap on_exit EXIT
  21.     fi
  22. }

退出时杀掉子进程

Trap表达式不一定需要单独的函数或者程序,它也可以直接使用复杂的表达式,如:

  1. trap ‘jobs -p | xargs kill’ EXIT

Bash使用示例(2) – 内部变量

$@

“$@”把所有的命令行参数作为一个数组返回。与”$*”不一样,它是作为一个字符串来返回。
“$@”可以通过循环来遍历所有元素,如下脚本:

  1. #!/bin/bash
  2. for var in "$*"; do
  3.     echo $var
  4. done

因为$*只把参数作为一个字符串返回,echo就只被调用一次:

  1. ~> $ ./testscript.sh firstarg secondarg thirdarg
  2. firstarg secondarg thirdarg

使用$@时:

  1. #!/bin/bash
  2. for var in "$@"; do
  3.     echo $var
  4. done

以数组返回所有参数,使用你能够单独地访问每一个参数:

  1. ~> $ ./testscript.sh firstarg secondarg thirdarg
  2. firstarg
  3. secondarg
  4. thirdarg

$#

获取命令行参数个数,键入:

  1. #!/bin/bash
  2. echo "$#"

如带3个参数运行脚本,输出如下:

  1. ~> $ ./testscript.sh firstarg secondarg thirdarg
  2. 3

$!

返回上一个程序执行的进程ID:

  1. ~> $ ls &
  2. testfile1 testfile2
  3. [1]+  Done                    ls
  4. ~> $ echo $!
  5. 21715

$$

当前进程的pid,如果在bash命令行下执行,相当于是bash进程的pid:

  1. ~> $ echo $$
  2. 13246

$*

以单个字符串返回所有命令行参数。
testscript.sh:

  1. #!/bin/bash
  2. echo "$*"

带几个参数运行脚本:

  1. ./testscript.sh firstarg secondarg thirdarg

输出:

  1. firstarg secondarg thirdarg

$?

返回上一次函数或命令执行的退出状态。通过0表示执行成功,其它的则表示执行失败:

  1. ~> $ ls *.blah;echo $?
  2. ls: cannot access *.blah: No such file or directory
  3. 2
  4. ~> $ ls;echo $?
  5. testfile1 testfile2
  6. 0

$1 $2 $3等

从命令行传递给脚本或者一个函数的位置参数:

  1. #!/bin/bash
  2. # $n is the n’th positional parameter
  3. echo "$1"
  4. echo "$2"
  5. echo "$3"

输出如下:
~> $ ./testscript.sh firstarg secondarg thirdarg

  1. firstarg
  2. secondarg
  3. thirdarg

如果位置参数的数量大于9,需要使用大括号:

  1. #  "set — " sets positional parameters
  2. set — 1 2 3 4 5 6 7 8 nine ten eleven twelfe
  3. echo $10   # outputs 1
  4. echo ${10} # outputs ten

$FUNCNAME

获取当前函数的名称:

  1. my_function()
  2. {
  3.     echo "This function is $FUNCNAME"    # This will output "This function is my_function"
  4. }

如果在函数外打印此变量:

  1. my_function
  2.  
  3. echo "This function is $FUNCNAME"    # This will output "This function is"

$HOME

用户的主目录

  1. ~> $ echo $HOME
  2. /home/user

$IFS

此变量包含用于循环中bash拆分字符串的内部字段分隔符。默认是空白字符n(newline),t(tab)或者空格。更改它的话使你能够使用不同分隔符拆分字符串:

  1. IFS=","
  2. INPUTSTR="a,b,c,d"
  3. for field in ${INPUTSTR}; do
  4.     echo $field
  5. done

输出:
a
b
c
d

$PWD

输出当前工作目录

  1. ~> $ echo $PWD
  2. /home/user
  3. ~> $ cd directory
  4. directory> $ echo $PWD
  5. /home/user/directory

$HOSTNAME

系统启动时分配的主机名

  1. ~> $ echo $HOSTNAME
  2. mybox.mydomain.com

$LINENO

输出脚本的当前行号。在调试脚本时可能会用到。

  1. #!/bin/bash
  2. # this is line 2
  3. echo something  # this is line 3
  4. echo $LINENO # Will output 4

Bash使用示例(1) – 数组

数组赋值

列表赋值

用新元素创建数组

  1. array=(‘first element’ ‘second element’ ‘third element’)

下标赋值

显式指定元素索引创建数组:

  1. array=([3]=’fourth element’ [4]=’fifth element’)

按索引赋值

  1. array[0]=’first element’
  2. array[1]=’second element’

按名称赋值(关联数组)

  1. declare -A array
  2. array[first]=’First element’
  3. array[second]=’Second element’

动态赋值

以其它命令的输出创建一个数组,例如使用seq创建一个从1到10的数组:

  1. array=(`seq 1 10`)

从脚本参数创建数组

  1. array=("$@")

循环内赋值

  1. while read -r; do
  2.     #array+=("$REPLY")     # Array append
  3.     array[$i]="$REPLY"     # Assignment by index
  4.     let i++                # Increment index
  5. done < <(seq 1 10)  # command substitution
  6. echo ${array[@]}    # output: 1 2 3 4 5 6 7 8 9 10

访问数组元素

打印索引为0的元素

  1. echo "${array[0]}"

打印最后一个元素(从Bash 4.3可用)

  1. echo "${array[-1]}"

打印从索引1开始的元素

  1. echo "${array[@]:1}"

打印从索引1开始的3个元素

  1. echo "${array[@]:1:3}"

数组更改

按索引更改

初始化或者更新数组中的一个特定元素

  1. array[10]="elevenths element"    # because it’s starting with 0

追回

修改数组,追加元素到数组结尾

  1. array+=(‘fourth element’ ‘fifth element’)

添加元素到数组开头

  1. array=("new element" "${array[@]}")

插入

给定索引值插入一个元素

  1. arr=(a b c d)
  2. # insert an element at index 2
  3. i=2
  4. arr=("${arr[@]:0:$i}" ‘new’ "${arr[@]:$i}")
  5. echo "${arr[2]}" #output: new

删除

使用uset删除指定索引元素

  1. arr=(a b c)
  2. echo "${arr[@]}"   # outputs: a b c
  3. echo "${!arr[@]}"  # outputs: 0 1 2
  4. unset -v ‘arr[1]’
  5. echo "${arr[@]}"   # outputs: a c
  6. echo "${!arr[@]}"  # outputs: 0 2

重排索引

当有一些元素从数组被删除时,可以使用下面方法重排索引,或者你不知道索引是否存在时隙时会有用。

  1. array=("${array[@]}")

数组长度

${#array[@]}可以得到${array[@]}数组的长度

  1. array=(‘first element’ ‘second element’ ‘third element’)
  2. echo "${#array[@]}" # gives out a length of 3

迭代数组元素

  1. fileList=( file1.txt file2.txt file3.txt )
  2.  
  3. # Within the for loop, $file is the current file
  4. for file in "${fileList[@]}"
  5. do
  6.   echo "$file"
  7. done