接触过一些 shell 脚本,做服务端运维时也时常用到,是时候专门学习一下了。
使用 _EOF_ 将多行语句作为单句,避免转义字符的麻烦:
# echosecho "<html>"echo "<head>"echo "</head>"echo "</html>"
echo "<html> <head> </head> </html>"
cat << _EOF_<html><head></head></html>_EOF_
Rules:
#!/bin/bashtitle="System Information for"echo $title
脚本文件启动前,系统已预设一些环境变量,在命令行中使用 printenv
查看这些变量:
[root@bhc004 ~]# printenvXDG_SESSION_ID=87HOSTNAME=bhc004TERM=linuxSHELL=/bin/bashHISTSIZE=10000SSH_CLIENT=125.117.180.232 57544 22SSH_TTY=/dev/pts/0USER=rootLS_COLORS=rs=0:di=01;34:ln=01;``````.spx=01;36:*.xspf=01;36:MAIL=/var/spool/mail/rootPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/jdk/binPWD=/rootLANG=en_US.UTF-8HISTCONTROL=ignoredupsSHLVL=1HOME=/rootLOGNAME=rootSSH_CONNECTION=125.117.180.232 57544 192.168.0.182 22LESSOPEN=||/usr/bin/lesspipe.sh %sXDG_RUNTIME_DIR=/run/user/0HISTTIMEFORMAT=%F %T root_=/usr/bin/printenv
在 shell 中可以使用这些环境变量 echo $HOSTNAME
。
Command Substitution and Constants
echo Updated on $(date +"%x %r %Z") by $USER
$(date+"%x %r %Z")
中 $()
告诉 shell 替换附带命令的执行结果,即插入 date+"%x %r %Z
的执行结果(当前时间)。
Be aware that there is an older, alternate syntax for "$(command)" that uses the backtick character "`". This older form is compatible with the original Bourne shell (sh). I tend not to use the older form since I am teaching modern
bash
here, notsh
, and besides, I think backticks are ugly. The bash shell fully supports scripts written for sh, so the following forms are equivalent: $(command) `command`
把命令执行结果赋值给变量:
right_now=$(date +"%x %r %z")
RIGHT_NOW=$(date +"%x %r %Z")TIME_STAMP="Updated on $RIGHT_NOW by $USER"
很少用,使用大写字母定义常量名(非强制)。
自定义函数
#!/bin/bash
#### Functionsshow_uptime() { echo "<h2>System uptime</h2>" echo "<pre>" uptime echo "</pre>"}
drive_space() { echo "<h2>Filesystem space</h2>" echo "<pre>" df echo "</pre>"}
cat << _EOF_ $(show_uptime) $(drive_space)_EOF_
function generate_instance_conf() { echo "configuring server $1"}generate_instance_conf server1
输出:
configuring server server1
Following are the list of available options for date command :
Format option | Part of Date | Desciption | Example Output |
---|---|---|---|
date +%a | Weekday | Name of weekday in short (like Sun, Mon, Tue, Wed, Thu, Fri, Sat) | Mon |
date +%A | Weekday | Name of weekday in full (like Sunday, Monday, Tuesday) | Monday |
date +%b | Month | Name of Month in short (like Jan, Feb, Mar ) | Jan |
date +%B | Month | Month name in full short (like January, February) | January |
date +%d | Day | Day of month (e.g., 01) | 04 |
date +%D | MM/DD/YY | Current Date; shown in MM/DD/YY | 02/18/18 |
date +%F | YYYY-MM-DD | Date; shown in YYYY-MM-DD | 2018-01-19 |
date +%H | Hour | Hour in 24-hour clock format | 18 |
date +%I | Hour | Hour in 12-hour clock format | 10 |
date +%j | Day | Day of year (001..366) | 152 |
date +%m | Month | Number of month (01..12) (01 is January) | 05 |
date +%M | Minutes | Minutes (00..59) | 52 |
date +%S | Seconds | Seconds (00..59) | 18 |
date +%N | Nanoseconds | Nanoseconds (000000000..999999999) | 300231695 |
date +%T | HH:MM:SS | Time as HH:MM:SS (Hours in 24 Format) | 18:55:42 |
date +%u | Day of Week | Day of week (1..7); 1 is Monday | 7 |
date +%U | Week | Displays week number of year, with Sunday as first day of week (00..53) | 23 |
date +%Y | Year | Displays full year i.e. YYYY | 2018 |
date +%Z | Timezone | Time zone abbreviation (Ex: IST, GMT) | IST |
You may use any of the above-mentioned format options (first column) for the date command in the aforementioned syntax.
if
语法如下:
if commands; thencommands[elif commands; thencommands...][elsecommands]fi
退出状态,即命令执行后会给系统一个值,0-255,0 代表 success:
[root@bhc004 ~]# true[root@bhc004 ~]# echo $?0[root@bhc004 ~]# false[root@bhc004 ~]# echo $?1
# First formtest expression# Second form[ expression ]
test 命令和 if 命令一起完成 true /false 判断,当表达式为 true,test 以 0 退出;为 false,test 以 1 退出。
例如:
if [ -f .bash_profile ]; then echo "You have a .bash_profile. Things are fine."else echo "Yikes! You have no .bash_profile!"fi
表达式 “-f .bashprofile” 表示 .bashprofile 是一个文件,若为 true,则执行 then
后的命令;否则执行 else 后的命令。
test 可以评估的表达式如下:
Expression | Description |
---|---|
-d file | True if file is a directory. |
-e file | True if file exists. |
-f file | True if file exists and is a regular file. |
-L file | True if file is a symbolic link. |
-r file | True if file is a file readable by you. |
-w file | True if file is a file writable by you. |
-x file | True if file is a file executable by you. |
file1 -nt file2 | True if file1 is newer than (according to modification time) file2 |
file1 -ot file2 | True if file1 is older than file2 |
-z string | True if string is empty. |
-n string | True if string is not empty. |
string1 = string2 | True if string1 equals string2. |
string1 != string2 | True if string1 does not equal string2. |
不同的书写格式:
# Alternate formif [ -f .bash_profile ]then echo "You have a .bash_profile. Things are fine."else echo "Yikes! You have no .bash_profile!"fi
# Another alternate formif [ -f .bash_profile ]then echo "You have a .bash_profile. Things are fine."else echo "Yikes! You have no .bash_profile!"fi
exit 命令可以立即结束此脚本,并设置退出状态:
exit 0
id
命令可以查看当前用户信息:
[root@bhc004 ~]# iduid=0(root) gid=0(root) groups=0(root)[root@bhc004 ~]# id -u0
if [ $(id -u) != "0" ]; then # >&2 输出到标准错误 echo "You must be the superuser to run this script" >&2 # 1 向操作系统表示脚本执行不成功 exit 1else echo "superuser"fi
在第一行加 +x
监控脚本执行状态:
#!/bin/bash -x
或者,使用 set-x
和 set+x
监控一段代码:
#!/bin/bash
number=1
set -xif [ $number = "1" ]; then echo "Number equals 1"else echo "Number does not equal 1"fiset +x
用户交互。
从键盘输入并赋值给变量:
#!/bin/bash
echo -n "Enter some text > "read textecho "You entered: $text"
-n
使输入和字符串在同一行。read 有 -t
、 -s
等参数:
-t
表示在指定时间内获得响应,当无论用户是否输入都要继续执行时使用;is
表示不显示输入的内容;当输入密码时使用;#!/bin/bash
echo -n "Hurry up and type something! > "if read -t 3 response; then echo "Great, you made it in time!"else echo "Sorry, you are too slow!"fi
使用双括号计算算术表达式:
#!/bin/bash
first_num=0second_num=0
echo -n "Enter the first number --> "read first_numecho -n "Enter the second number -> "read second_num
echo "first number + second number = $((first_num + second_num))"echo "first number - second number = $((first_num - second_num))"echo "first number * second number = $((first_num * second_num))"echo "first number / second number = $((first_num / second_num))"echo "first number % second number = $((first_num % second_num))"echo "first number raised to the"echo "power of the second number = $((first_num ** second_num))"
括号内的变量不需要 $
就可以引用,
#!/bin/bash
number=0
echo -n "Enter a number > "read number
echo "Number is $number"if [ $((number % 2)) -eq 0 ]; then echo "Number is even"else echo "Number is odd"fi
更过分支:
#!/bin/bash
echo -n "Enter a number between 1 and 3 inclusive > "read characterif [ "$character" = "1" ]; then echo "You entered one."elif [ "$character" = "2" ]; then echo "You entered two."elif [ "$character" = "3" ]; then echo "You entered three."else echo "You did not enter a number between 1 and 3."fi
使用 case 优化分支结构
case 语句格式如下:
case word in patterns ) commands ;;esac
改造如上语句:
#!/bin/bash
echo -n "Enter a number between 1 and 3 inclusive > "read charactercase $character in 1 ) echo "You entered one." ;; 2 ) echo "You entered two." ;; 3 ) echo "You entered three." ;; * ) echo "You did not enter a number between 1 and 3."esac
case 也可以匹配表达式:
#!/bin/bash
echo -n "Type a digit or a letter > "read charactercase $character in # Check for letters [[:lower:]] | [[:upper:]] ) echo "You typed the letter $character" ;;
# Check for digits [0-9] ) echo "You typed the digit $character" ;;
# Check for anything else * ) echo "You did not type a letter or a digit"esac
*
通常用来检测无效输入。
#!/bin/bash
number=0while [ "$number" -lt 10 ]; do echo "Number = $number" number=$((number + 1))done
#!/bin/bash
number=0until [ "$number" -ge 10 ]; do echo "Number = $number" number=$((number + 1))done
#!/bin/bash
selection=until [ "$selection" = "0" ]; do echo " PROGRAM MENU 1 - Display free disk space 2 - Display free memory
0 - exit program" echo -n "Enter selection: " read selection echo "" case $selection in 1 ) df ;; 2 ) free ;; 0 ) exit ;; * ) echo "Please enter 1, 2, or 0" esacdone
#!/bin/bash
echo "Positional Parameters"echo '$0 = ' $0echo '$1 = ' $1echo '$2 = ' $2echo '$3 = ' $3
if [ "$1" != "" ]; then echo "Positional parameter 1 contains something"else echo "Positional parameter 1 is empty"fi
interactive=filename=~/sysinfo_page.html
while [ "$1" != "" ]; do case $1 in -f | --file ) shift filename=$1 ;; -i | --interactive ) interactive=1 ;; -h | --help ) usage exit ;; * ) usage exit 1 esac shiftdone
shift
是内建命令,每次调用 shift
使所有参数的索引减一,即 $2 变成 $1,$3 变成 $2:
#!/bin/bash
echo "You start with $# positional parameters"
# Loop until all parameters are used upwhile [ "$1" != "" ]; do echo "Parameter 1 equals $1" echo "You now have $# positional parameters"
# Shift all the parameters down by one shift
done
#!/bin/bash
# sysinfo_page - A script to produce a system information HTML file
##### Constants
TITLE="System Information for $HOSTNAME"RIGHT_NOW=$(date +"%x %r %Z")TIME_STAMP="Updated on $RIGHT_NOW by $USER"
##### Functions
system_info(){ echo "<h2>System release info</h2>" echo "<p>Function not yet implemented</p>"
} # end of system_info
show_uptime(){ echo "<h2>System uptime</h2>" echo "<pre>" uptime echo "</pre>"
} # end of show_uptime
drive_space(){ echo "<h2>Filesystem space</h2>" echo "<pre>" df echo "</pre>"
} # end of drive_space
home_space(){ # Only the superuser can get this information
if [ "$(id -u)" = "0" ]; then echo "<h2>Home directory space by user</h2>" echo "<pre>" echo "Bytes Directory" du -s /home/* | sort -nr echo "</pre>" fi
} # end of home_space
write_page(){ cat <<- _EOF_ <html> <head> <title>$TITLE</title> </head> <body> <h1>$TITLE</h1> <p>$TIME_STAMP</p> $(system_info) $(show_uptime) $(drive_space) $(home_space) </body> </html>_EOF_
}
usage(){ echo "usage: sysinfo_page [[[-f file ] [-i]] | [-h]]"}
##### Main
interactive=filename=~/sysinfo_page.html
while [ "$1" != "" ]; do case $1 in -f | --file ) shift filename=$1 ;; -i | --interactive ) interactive=1 ;; -h | --help ) usage exit ;; * ) usage exit 1 esac shiftdone
# Test code to verify command line processing
if [ "$interactive" = "1" ]; then response=
echo -n "Enter name of output file [$filename] > " read response if [ -n "$response" ]; then filename=$response fi
if [ -f $filename ]; then echo -n "Output file exists. Overwrite? (y/n) > " read response if [ "$response" != "y" ]; then echo "Exiting program." exit 1 fi fifiecho "output file = $filename"
# Write page (comment out until testing is complete)
# write_page > $filename
for 命令可以处理命令行参数列表:
#!/bin/bash
for i in "$@"; do echo $idone
比较两个文件夹中的文件:
#!/bin/bash
# cmp_dir - program to compare two directories
# Check for required argumentsif [ $# -ne 2 ]; then echo "usage: $0 directory_1 directory_2" 1>&2 exit 1fi
# Make sure both arguments are directoriesif [ ! -d $1 ]; then echo "$1 is not a directory!" 1>&2 exit 1fi
if [ ! -d $2 ]; then echo "$2 is not a directory!" 1>&2 exit 1fi
# Process each file in directory_1, comparing it to directory_2missing=0for filename in $1/*; do fn=$(basename "$filename") if [ -f "$filename" ]; then if [ ! -f "$2/$fn" ]; then echo "$fn is missing from $2" missing=$((missing + 1)) fi fidoneecho "$missing files missing"
home_space(){ echo "<h2>Home directory space by user</h2>" echo "<pre>" format="%8s%10s%10s %-s\n" printf "$format" "Dirs" "Files" "Blocks" "Directory" printf "$format" "----" "-----" "------" "---------" if [ $(id -u) = "0" ]; then dir_list="/home/*" else dir_list=$HOME fi for home_dir in $dir_list; do total_dirs=$(find $home_dir -type d | wc -l) total_files=$(find $home_dir -type f | wc -l) total_blocks=$(du -s $home_dir) printf "$format" $total_dirs $total_files $total_blocks done echo "</pre>"
} # end of home_space
使用 printf 格式化输出,参考:
提高 shell 脚本的鲁棒性,处理 exit status,否则会发生意想不到的事情。
cd $some_directoryrm *
若 $somedirectory 不存在,进入指定目录失败,则删除当前工作文件夹下的所有文件,--
#!/bin/bash
# Program to print a text file with headers and footers
TEMP_FILE=/tmp/printfile.txt
clean_up() {
# Perform program exit housekeeping rm $TEMP_FILE exit}
trap clean_up SIGHUP SIGINT SIGTERM
pr $1 > $TEMP_FILE
echo -n "Print file? [y/n]: "readif [ "$REPLY" = "y" ]; then lpr $TEMP_FILEficlean_up
结束一个脚本时,应清理一些文件:
#!/bin/bash
# Program to print a text file with headers and footers
# Usage: printfile file
# Create a temporary file name that gives preference# to the user's local tmp directory and has a name# that is resistant to "temp race attacks"
if [ -d "~/tmp" ]; then TEMP_DIR=~/tmpelse TEMP_DIR=/tmpfiTEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOMPROGNAME=$(basename $0)
usage() {
# Display usage message on standard error echo "Usage: $PROGNAME file" 1>&2}
clean_up() {
# Perform program exit housekeeping # Optionally accepts an exit status rm -f $TEMP_FILE exit $1}
error_exit() {
# Display error message and exit echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 clean_up 1}
trap clean_up SIGHUP SIGINT SIGTERM
if [ $# != "1" ]; then usage error_exit "one file to print must be specified"fiif [ ! -f "$1" ]; then error_exit "file $1 cannot be read"fi
pr $1 > $TEMP_FILE || error_exit "cannot format file"
echo -n "Print file? [y/n]: "readif [ "$REPLY" = "y" ]; then lpr $TEMP_FILE || error_exit "cannot print file"ficlean_up
/tmp
会有很多程序放置临时文件在里面,文件名重复会覆盖内容(可预测,predictable);
建议创建本地临时文件夹 ~/tmp
,即用户目录下的子目录;
TEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOM
/tmp
或 ~/tmp
这样就生成了唯一且不易重复的文件名,例如 tomcat
:
tomcat.5141431142496395497.9000tomcat.6486338835837423954.9000tomcat.8674130370990688323.9000tomcat-docbase.2197772788338189182.9000tomcat-docbase.30706145223021005.9000
http://linuxcommand.org/lc3writingshell_scripts.php
(完)