variable=name
注意,=两边不能有空格。一切变量都会被视为字符串。如果要使用局部变量则要加关键字local。
echo ${variable}
其实这是参数和变量扩展。关于echo的详细用法在下面章节。
unset variable
注意,不能在变量前加$。
export variable
同样不能加$。环境变量将在子Shell中可见,但是当前Shell的环境变量不会影响父Shell。
环境变量存储了有关Shell会话和工作环境的信息。一般情况下,用户登录成功后,会启动其Shell,这个Shell叫做登录Shell。这个Shell自带一些全局的环境变量,并且它会运行相应的配置文件,初始化部分环境变量。所以当我们要定义一些自己的变量时就要定义在这些配置文件中,这样启动时便会定义了这些环境变量。另外环境变量在Shell脚本中是可以直接引用的。下面讲解会运行脚本。
登录Shell:
配置文件 | 说明 |
---|---|
/etc/profile | 全局配置文件。如果存在则读取 |
~/.bash_profile | 如果存在则读取 |
~/.bash_login | 如果上个文件不存在,则读取,否则不读取 |
~/.profile | 如果上个文件不存在,则读取,否则不读取 |
可以发现,用户的当前目录下的三个隐藏的配置文件有优先级,且且只读取第一个找到的配置文件。这里先看下全局配置文件。
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
if [ "$PS1" ]; then
if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
# The file bash.bashrc already sets the default PS1.
# PS1='\h:\w\$ '
if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fi
else
if [ "`id -u`" -eq 0 ]; then
PS1='# '
else
PS1='$ '
fi
fi
fi
# The default umask is now handled by pam_umask.
# See pam_umask(8) and /etc/login.defs.
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_45
export CLASSPATH=.:$JAVA_HOME/lib
export MYSQL_HOME=/usr/local/mysql
export CATALINA_BASE=/opt/apache-tomcat-8.0.28
export CATALINA_HOME=/opt/apache-tomcat-8.0.28
export TOMCAT_HOME=/opt/apache-tomcat-8.0.28
export SQLITE_HOME=/home/gzx/sqlite3
export M2_HOME=/home/gzx/apache-maven-3.3.9
export PATH=$JAVA_HOME/bin:$MYSQL_HOME/bin:$TOMCAT_HOME/bin:$SQLITE_HOME/bin:$M2_HOME/bin:$PATH
这个文件是所有用户公用的,所以定义在这里的环境变量所有用户Shell都有效。
在上面的脚本中,在PS1变量存在的情况下,判断是否已经有BASH这个环境变量,而且该环境变量不是/bin/sh,则在当前Shell执行存在的文件/etc/bash.bashrc(这个文件的内容在下一部分解释)。否则设置PS1:对于超级用户,则PS1为#,对于普通用户则为$。本人是第一种情况。
接着执行/etc/profile.d下的sh脚本,最后导出一些自定义的全局环境变量。/etc/profile.d目录下可以自定义一些脚本。
在本人的用户目录下,只有一个.profile文件。
# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.
# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022
# if running bash
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
在BASH_VERSION变量非空且~/.bashrc存在的情况下,执行该脚本。本人的机子会执行该脚本(留到下一节)。如果~/bin目录存在,则将该目录加到PATH环境变量中。
当我们在桌面系统中打开一个伪模拟终端时,会启动一个交互式bash。这个bash也会执行配置文件/etc/bash.bashrc和~/.bashrc。而这两个文件刚好也会在登录Shell中执行。
/etc/bash.bashrc这个文件基本没做什么。
# System-wide .bashrc file for interactive bash(1) shells.
# To enable the settings / commands in this file for login shells as well,
# this file has to be sourced in /etc/profile.
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, overwrite the one in /etc/profile)
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
# Commented out, don't overwrite xterm -T "title" -n "icontitle" by default.
# If this is an xterm set the title to user@host:dir
#case "$TERM" in
#xterm*|rxvt*)
# PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
# ;;
#*)
# ;;
#esac
# enable bash completion in interactive shells
#if ! shopt -oq posix; then
# if [ -f /usr/share/bash-completion/bash_completion ]; then
# . /usr/share/bash-completion/bash_completion
# elif [ -f /etc/bash_completion ]; then
# . /etc/bash_completion
# fi
#fi
# sudo hint
if [ ! -e "$HOME/.sudo_as_admin_successful" ] && [ ! -e "$HOME/.hushlogin" ] ; then
case " $(groups) " in *\ admin\ *|*\ sudo\ *)
if [ -x /usr/bin/sudo ]; then
cat <<-EOF
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
EOF
fi
esac
fi
# if the command-not-found package is installed, use it
if [ -x /usr/lib/command-not-found -o -x /usr/share/command-not-found/command-not-found ]; then
function command_not_found_handle {
# check because c-n-f could've been removed in the meantime
if [ -x /usr/lib/command-not-found ]; then
/usr/lib/command-not-found -- "$1"
return $?
elif [ -x /usr/share/command-not-found/command-not-found ]; then
/usr/share/command-not-found/command-not-found -- "$1"
return $?
else
printf "%s: command not found\n" "$1" >&2
return 127
fi
}
fi
最后再看下.bashrc这个配置文件。
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth
# append to the history file, don't overwrite it
shopt -s histappend
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# If set, the pattern "**" used in a pathname expansion context will
# match all files and zero or more directories and subdirectories.
#shopt -s globstar
# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color) color_prompt=yes;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# colored GCC warnings and errors
#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Add an "alert" alias for long running commands. Use like so:
# sleep 10; alert
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi
unset JAVA_TOOL_OPTIONS
function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local tmp=$[ $1 - 1 ]
local res=`factorial $tmp`
local ret=$[ $res * $1 ]
echo $ret
fi
}
PATH=.:$PATH
显然,这个文件只能在交互式启动时才能执行。会初始化历史记录的长度变量HISTSIZE。同时定义了一些命令的别名,如ls、grep会自动显示颜色。ll和l都是命令别名。最后自己定义了一个递归的阶乘函数factorial,以及在PATH前面追加一个当前目录。这个配置文件用于存放用户的一些自定义的脚本。如果在交互式Shell再运行bash,则会再次运行.bashrc。对于脚本,其运行时的Shell在最开头定义#!/bin/bash,这是非交互式Shell,一般不会运行任何配置文件。
环境变量名 | 含义 | 注意事项 |
---|---|---|
USER | 当前用户名 | |
HOME | 用户主目录 | |
PATH | 冒号分割的Shell查找命令的目录列表 | 查找EXE命令,且一般包含的是bin目录 |
IFS | Shell用来分割字符串的一系列字符 | read、awk以及单词分割都会使用这个环境变量。其值一般为’空格\t\n’ |
array=(hello world you are 212)
数组元素用空格隔开,而不是用逗号。
echo ${array[1]}
注意,对于数组要加花括号。第0项有两种输出方法:
echo ${array[0]}
echo $array
echo "${array[@]}"
也可以用循环语句逐项输出。
echo ${#array[@]}
注意,未初始化的元素不会参与计数。这与其他语言不同。
array+=(d e f)
注意+=不能出现空格。
array_sorted=$(for i in "${array[@]}"; do echo $i; done | sort)
注意sort排序使用的是本地化设置,小写字母会出现在大写字母前面。
下面的list表示的是命令、test命令、复合命令[[]]、复合命令(()),以及用&&、||等连接上述命令的表达式。
if list; then
cmd1
elif list; then
cmd2
else
cmd3
fi
注意,使用方法和C语言类似。list命令执行成功则会执行对应的语句。
case variable in
pattern1 | pttern2) cmd1;;
pattern3 | pattern4) cmd2;;
*) cmd3;;
esac
注意上面要用;;结束,表示当前项匹配时,不会继续往下匹配。最后要用case反过来写的esac结束。对于每一项,可以使用|来提供相同命令的执行。最后一项是默认。这里的pattern和路径名匹配类似。
for variable in word; do
cmd
done
这里的word可以是一个字符串,或者是一个命令的执行结果,即命令替换,或者数组(一般要用引号包围),但是用$IFS分割。cmd使用variable时必须加$,因为它是一个变量。另外这个命令还可以直接输出或者重定向,即在done后加>或者|,将for的输出重定向,或者通过管道输出。
for (( expr1; expr2; expr3 )); do
cmd
done
这是一种C语言风格的for语句,用法跟C语言类似。但是expr必须是算术表达式,不用加(()),可以加任意空格。
while list; do
cmd
done
跟C语言类似。只要list成立,则不断循环。
until list; do
cmd
done
这个语句表示当list成立时,退出循环。与while的语义正好相反。while和until语句都可以使用break和continue这两个控制命令,与C语言用法类似。
test命令的格式如下:
test expression
但是我们更常用它的等价命令:
[ expression ]
注意这个命令的两边需要有空格,不然会报错。expression可以分为三类,分别是数值比较、字符串比较、文件比较。其中数值比较可以使用算术表达式(())替代。而test命令也有更高级更新的形式[[]]复合命令,它还支持字符串模式匹配。
arg1 OP arg2
这里arg是数值,而OP是-eq, -ge, -gt, le, -lt, -ne。
#字符串长度非空
-n string
#字符串长度为空
-z string
#字符串相等,[[]]还可以用==,同时它还支持模式匹配
string1 = string2
string1 != string2
#使用ASCII排序。对于test命令必须用\对<和>转义,[[]]不用。
string1 < string2
string1 > string2
#文件是否存在
-e file
#文件存在且是目录
-d file
#文件存在且是普通文件
-f file
#文件存在且可读
-r file
#文件存在且可写
-w file
#文件存在且可执行
-x file
#文件更新,file2可以不存在
file1 -nt file2
#文件更老,file1可以不存在
file1 -ot file2
格式是
$((expression))
其中$可以省略。算术表达式专门用于数值运算。expression如下,优先级相同的放在一组,同时优先级从上到下递减。
id++ id--
++id --id
- + #正负号
! ~ #逻辑取反和位取反
** #幂运算
* / % #乘除取余
+ - #加减
<< >> #左右位移
<= >= < > #比较
== != #相等和不等
& #按位与
^ #按位异或
| #按位或
&& #逻辑与
|| #逻辑或
expr?expr:expr
= *= /= %= += -= <<= >>= &= ^= |=
expr1 , expr2 #逗号表达式
(())里面的变量的 可以去掉,如果变量名没有定义将用0代替。null也会视为0。可以使用<、>、==比较数值大小。这里的=是赋值,与C语言类似。注意test中字符串相等是=,而[[]]可以是==。
#字符串长度
${#parameter}
#从offset开始的length个字符
${parameter:offset:length}
#从头开始去除最短匹配
${parameter#pattern}
#从开头开始去除最长匹配
${parameter##pattern}
#从尾开始去除最短匹配
${paramter%pattern}
#从尾开始去除最长匹配
${parameter%%pattern}
#从头替换第一个字符串
${paramter/pattern/string}
${paramter/#pattern/string}
#从尾替换第一个字符串
${paramter/%pattern/string}
#替换全部匹配的字符串
${parameter//pattern/string}
注意,当没有给出string时,将删除pattern。
read的格式如下:
read -t timeout -p prompt -s var1 var2 var3...
read将使用$IFS对输入的一行进行分割,以换行符作为读入结束的标志。其中,-t表示等待多少秒,超时没有输入则返回。-p输出提示字符。-s表示输入不回显,一般在输入密码时使用。后面的var1表示接收的变量名。read将分割的第一项给var1,第二项给var2,第三项给var3。对于多出的项全部给最后一项,对于少的置空。如果没有给出任何参数,则默认给变量$REPLY。
由于这个命令是一行一行处理,所以可以用于处理文件。不过它不支持从管道读取。
格式如下:
cat <<- _EOF_
str
_EOF_
<<-后面的-表示忽略开始的tab,这样_EOF_就不用顶格写。_EOF_用于表示输入的起始和结束。
cat <<< word
默认情况下,echo会输出一个换行符号。如果不要输出换行符号,则必须加-n选项:
echo -n ${variable}
如果要对转移字符进行解释,如\n,则必须加-e选项:
echo -e ${variable}
这是格式化输出语句,默认不会有换行,和C语言用法基本一致。
printf format var1 var2 ...
注意,后面的变量要加$。
函数的格式:
function func_name(){
}
这里funciton关键字和括号可以省略其中一个。因为这里的函数的参数不会通过括号里给出,而且传递的参数的个数是任意的,所以括号没什么必要。那么在函数内部这些传递的参数是怎么获取到的呢?这是通过位置参数来获取的。
位置参数的格式是$1, …, $9, ${10}, ${11}…。注意大于9的位置参数要加花括号,而且参数是从1开始算起的。另外,$0表示的脚本文件的名字,$#表示位置参数的个数。不仅可以给函数传递参数,还可以给脚本传递参数,而且获取的都是这些位置参数。获取所有参数的变量是”$@”。
另外shift可以将位置参数整体往前移动一位,所以在有shift的while循环中,每次只需判断和利用\$1就可以了。
脚本中可以设置信号句柄,格式是:
trap cmd[func_handler] signal_name...
其中,第一个参数命令后者函数句柄,第二个参数是信号的名字或者信号号码。如SIGINT,SIGTERM。
对于首行没有#!/bin/bash的且没有可执行权限的文件,可以在当前Shell运行:
source filename
. filename
对于一般的文件,则要在首行添加#!/bin/bash,并修改可执行权限。
chmod u+x filename
运行时脚本的目录必须在PATH目录下,或者当前目录.在PATH目录下,这样可以直接用文件名运行。
filename
最后一种方法,可以直接用完整路径名运行。
#脚本在当前工作目录中
./filename
#任意目录
full_direcory/filename
主要有两种方式,一种是在将自己的脚本写在/etc/rc.local里面,这样开机时init会创建该进程。另一种是在/etc/profile.d下放置自己的sh脚本,这样用户登录时会执行。或者在~/.bashrc中定义自己的脚本,每次打开一个交互式终端,都会执行。
当控制终端会话退出时,会收到一个SIGHUP信号,这时所有在这个终端上的进程都会被终止。如果要使进程能够继续运行,则必须屏蔽该信号,即
nohup script &
但该脚本的输出将导出到脚本所在目录下的nohup.out中。