Linux Shell 基础

接触过一些 shell 脚本,做服务端运维时也时常用到,是时候专门学习一下了。

基础

Here Script

使用 _EOF_ 将多行语句作为单句,避免转义字符的麻烦:

# echosecho "<html>"echo "<head>"echo "</head>"echo "</html>"
echo "<html> <head> </head> </html>"
cat << _EOF_<html><head></head></html>_EOF_

Variables

Rules:

  1. Names must start with a letter.
  2. A name must not contain embedded spaces. Use underscores instead.
  3. You cannot use punctuation marks.
#!/bin/bashtitle="System Information for"echo $title

Environment Variables

脚本文件启动前,系统已预设一些环境变量,在命令行中使用 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, not sh, 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

date 格式化

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 语法如下:

if commands; thencommands[elif commands; thencommands...][elsecommands]fi

Exit Status

退出状态,即命令执行后会给系统一个值,0-255,0 代表 success:

[root@bhc004 ~]# true[root@bhc004 ~]# echo $?0[root@bhc004 ~]# false[root@bhc004 ~]# echo $?1

test

# 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 命令可以立即结束此脚本,并设置退出状态:

exit 0

test for superuser

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

Watching your script

在第一行加 +x 监控脚本执行状态:

#!/bin/bash -x

或者,使用 set-xset+x监控一段代码:

#!/bin/bash
number=1
set -xif [ $number = "1" ]; then    echo "Number equals 1"else    echo "Number does not equal 1"fiset +x

键盘输入和算术

用户交互。

read

从键盘输入并赋值给变量:

#!/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

流控制 2

更过分支:

#!/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 语句格式如下:

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

* 通常用来检测无效输入。

循环

while

#!/bin/bash
number=0while [ "$number" -lt 10 ]; do    echo "Number = $number"    number=$((number + 1))done

util

#!/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 是内建命令,每次调用 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 循环

处理位置参数

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

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 格式化输出,参考:

  • GNU Awk User's Guide - Control Letters
  • GNU Awk User's Guide - Format Modifiers

其它

鲁棒性

提高 shell 脚本的鲁棒性,处理 exit status,否则会发生意想不到的事情。

cd $some_directoryrm *

若 $somedirectory 不存在,进入指定目录失败,则删除当前工作文件夹下的所有文件,--

clean_up 功能

#!/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
  • $TEMP_DIR 是 /tmp~/tmp
  • printfile 是应用程序的名字
  • $$ 是 shell 变量,将 process id (pid) 嵌入文件名
  • $RANDOM 是追加随机数

这样就生成了唯一且不易重复的文件名,例如 tomcat

tomcat.5141431142496395497.9000tomcat.6486338835837423954.9000tomcat.8674130370990688323.9000tomcat-docbase.2197772788338189182.9000tomcat-docbase.30706145223021005.9000

参考

http://linuxcommand.org/lc3writingshell_scripts.php

(完)

本文分享自微信公众号 - 董亮亮的开发笔记(liangl_dong),作者:dongliangliang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Shell 脚本自动部署 Redis 集群

    我时常需要在云服务器上搭建测试环境,每次入手新的服务器配置集群环境时,跟着笔记敲一遍命令行挺麻烦的。学了 shell 脚本后,我尝试使用脚本在单机服务器上部署 ...

    用户2987604
  • 算法 - PNPoly解决点和多边形问题

    计算点到多边形最短距离的基本原理是:依次计算点到多边形每条边的距离,然后筛选出最短距离。

    用户2987604
  • IDEA的主题等环境配置

    选择一个自己喜欢的配色方案,比找个漂亮的女朋友更重要,因为你和IDEA相处的时间比她还要多。

    用户2987604
  • 批处理一键安装JDK/一键安装JRE和自动配置Java环境变量及Tomcat的安装

    下面的批处理文件能够自动完成jdk的安装,tomcat的安装,web应用的部署,环境变量的注册,tomcat服务的安装和自动启动,但是具体到个人系统上,有待考证...

    浩Coding
  • Mi8刷机若干踩坑

    这个刷机的事情本来挺简单个事,但是我刷面具的时候,哪个面具是个坏包,之后的就触摸不正常了,就是点上去没有反应.很难受我又刷回去了miui然后再刷回来.本...

    云深无际
  • Linux常用命令13 - echo

    echo 命令是 Linux 中最基本和最常用的命令之一。 传递给 echo 的参数被打印到标准输出中。

    叉叉敌
  • 终端可以是丰富多彩的! echo实用技巧

    如果善用echo,我们可以一行命令搞定echo {"registry-mirrors": ["https://registry.docker...

    zhaoolee
  • 用CMD批处理脚本来守护进程

    Inkedus
  • windows nginx 管理脚本

    用户2657851
  • bash 的变量和参数

    对一个编程脚本来说,最最基础的当然是变量。 对大多数开发者来说,变量也是最不值得的大说特说的。 但 bash 里的变量有一些特别的地方值得说说,谨防跌坑。 基本...

    IMWeb前端团队

扫码关注云+社区

领取腾讯云代金券