编写shell脚本(二)

读取键盘输入

read 从标准输入中读取一行

也可以从文件重定向过来

read [-options] [variable...]
echo -n "Please enter an integer -> "
read int

-n使得输出后不换行
read可以给多个变量赋值

#!/bin/bash
echo -n "enter one or more values ->"
read var1 var2 var3 var4 var5
echo "var1= '$var1'"
echo "var2= '$var2'"
echo "var3= '$var3'"
echo "var4= '$var4'"
echo "var5= '$var5'"
enter one or more values ->asb jskdj kja e
var1= 'asb'
var2= 'jskdj'
var3= 'kja'
var4= 'e'
var5= ''

[me@linuxbox ~]$ read-multiple
Enter one or more values > a b c d e f g
var1 = 'a'
var2 = 'b'
var3 = 'c'
var4 = 'd'
var5 = 'e f g'

如果read命令接受到变量值数目少于期望的数字,那么额外的变量值为空,多余的输入数据则会 被包含到最后一个变量中

enter one or more values -> abd djkj dj
REPLY= ' abd djkj dj'

在这里插入图片描述
-p 选项 提供提示信息

read -p "enter one or more values >"
if read -t 10 -sp "enter secret pass phrase >" secret_pass;  then
	echo -e "\n secret pass phrase = '$secret_pass'"
else
	echo -e "\n time out" >&2
	exit 1
fi

  1. 不需要加[[ ]]
  2. 要指定-e才能开启转义,转义字符必须用单引号或者双引号引起来

IFS

将输入分隔,分发给多个变量
shell variable named IFS (for Internal Field Separator)
The default value of IFS contains a space, a tab, and a newline character, each of which will separate items from one another.

file=/etc/passwd
read -p "please input a user name -> " user_name
file_info=$(grep ^$user_name $file)
if [[ -n $file_info ]]; then
	IFS=":" read user pw uid gid name home shell <<< $file_info
	echo "User='$user'"
	echo "pw='$pw'"
else
	echo "no such user" >&2
	exit 1
	#statements
fi
  1. <<< 就是将后面的内容作为前面命令的标准输入,The <<< operator indicates a here string. A here string is like a here document, only shorter, consisting of a single string. In our example, the line of data from the /etc/passwd file is fed to the standard input of the read command.
  2. IFS=":" read user pw uid gid name home shell <<< "$file_info" Shell 允许在一个命令之前给一个或多个变量赋值。这些赋值会暂时改变之后的命令的环境变量, 有效时间是命令持续时间
  3. can’t pipe read

合法输入

[[:digit:]]* *代表必须匹配0或多次(要区分正则表达式和shell通配符)
‘.5656’ is a floating point number.
+ 必须匹配1次或多次

function invalid_input(){
	echo "invalid input '$REPLY'" >&2
	exit 1
}
read -p "enter a single item -> "
[[ -z $REPLY ]] && invalid_input #empty
[[ $(echo $REPLY | wc -w) -gt 1 ]] && invalid_input #more than one
# is input a valid filename?
if [[ $REPLY =~ ^[-[:alnum:]/\._]+$ ]]; then
	echo "$REPLY is a valid filename."
	if [[ -e "$REPLY" ]]; then
		echo "and file $REPLY exsits."
	else
		echo "however it doesnot exsit."
	fi
else
	echo "$REPLY is not a valid filename."
fi

while/until

#!/bin/bash
count=1
while [[ $count -le 5 ]]; do
	echo $count
	count=$((count+1))
done
echo "finished"
delay=3
while [[ $REPLY != 0 ]]; do
	clear
	echo "
	please select:
	1. display system information
	2. display disk space
	3. display home space utilization
	0. exit 
	"
	read -p "enter selection [0..3] ->"
	if [[ $REPLY =~ ^[0-3]$ ]]; then
		if [[ $REPLY == 0 ]]; then
			echo "programme terminates"
			exit
		fi
		if [[ $REPLY == 1 ]]; then
			echo "hostname is $HOSTNAME"
			uptime
			sleep $delay
		fi
		if [[ $REPLY == 2 ]]; then
			df -h 
			sleep $delay
			
		fi
		if [[ $REPLY == 3 ]]; then
			du -sh $HOME
			sleep $delay
		fi
	else
		echo "invalid entry"
		sleep $delay
	fi
done
echo "programme terminates"

跳出循环


delay=3
while true; do
	clear
	echo "
	please select:
	1. display system information
	2. display disk space
	3. display home space utilization
	0. exit 
	"
	read -p "enter selection [0..3] ->"
	if [[ $REPLY =~ ^[0-3]$ ]]; then
		if [[ $REPLY == 0 ]]; then
			break
		fi
		if [[ $REPLY == 1 ]]; then
			echo "hostname is $HOSTNAME"
			uptime
			sleep $delay
			continue
		fi
		if [[ $REPLY == 2 ]]; then
			df -h 
			sleep $delay
			continue
			
		fi
		if [[ $REPLY == 3 ]]; then
			du -sh $HOME
			sleep $delay
			continue
		fi
	else
		echo "invalid entry"
		sleep $delay
		continue
	fi
done
echo "programme terminates"

until

count=1
until [[ $count -gt 5 ]]; do
	echo $count
	count=$((count+1))
done
echo "finished"

1 2 3 4 5

循环读取文件

while IFS=":" read user pw uid gid name home shell; do
	printf "user: %s\t pw: %s\t uid: %s\n" $user $pw $uid
done < /etc/passwd
oot@ubuntu:/code# ./file.sh 
user: root	 pw: x	 uid: 0
user: daemon	 pw: x	 uid: 1
user: bin	 pw: x	 uid: 2
user: sys	 pw: x	 uid: 3
user: sync	 pw: x	 uid: 4
user: games	 pw: x	 uid: 5
user: man	 pw: x	 uid: 6
user: lp	 pw: x	 uid: 7
user: mail	 pw: x	 uid: 8
user: news	 pw: x	 uid: 9
user: uucp	 pw: x	 uid: 10
user: proxy	 pw: x	 uid: 13

疑难排解

语法错误

#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
    echo "Number is equal to 1.
else
    echo "Number is not equal to 1."
fi
script.sh: line 7: unexpected EOF while looking for matching `"'
script.sh: line 9: syntax error: unexpected end of file
  1. echo的引号丢失右引号,会匹配到下一个echo的左引号,第二个echo的右引号(现在变成左引号)找不到可以匹配的,同时把fi包含了,if找不到匹配
  2. 语法高亮可以排查

丢失或意外的标记

#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ] then
    echo "Number is equal to 1." 
	echo "Number is not equal to 1."; then 
	echo what
else echo hh
fi
Number is equal to 1.
Number is not equal to 1.
what
script.sh: line 4: [: missing `]'
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ] then
    echo "Number is equal to 1." 
	false; then 
	echo what
else echo hh
fi
Number is equal to 1.
hh
script.sh: line 4: [: missing `]'
  1. then 后面必须执行语句,不然fi匹配不上
  2. if后面接收多个命令,但是只以最后一个命令返回值为准

意外展开

#!/bin/bash
# trouble: script to demonstrate common errors
number=
if [ $number = 1 ]; then
    echo "Number is equal to 1."
else
    echo "Number is not equal to 1."
fi
Number is not equal to 1.
script.sh: line 4: [: =: unary operator expected

  1. 展开后变成[ =1 ] =是二元操作符 这时候test command会向要个类似于-z这样的一元操作符
  2. 通过给参数添加双引号“”,可以更正这个问题[ “” = 1 ]
  3. 同样地,引号应该被用在会被展开为多单词字符串的数值,尤其提防含空格的

逻辑错误

  • Incorrect conditional expressions. It’s easy to incorrectly code an if/then/else and have the wrong logic carried out. Sometimes the logic will be reversed or it will be incomplete. 条件表达式错误

  • “Off by one” errors. When coding loops that employ counters, it is possible to overlook that the loop may require the counting start with zero, rather than one, for the count to conclude at the correct point. These kinds of errors result in either a loop “going off the end” by counting too far, or else missing the last iteration of the loop by terminating one iteration too soon. 循环终止条件错误

Unanticipated situations. Most logic errors result from a program encountering data or situations that were unforeseen by the programmer. This can also include unanticipated expansions, such as a filename that contains embedded spaces that expands into multiple command arguments rather than a single filename. 展开错误

防错编程

cd $dir_name
rm *

如果dir_name不存在,就会在当前的目录执行删除操作。
make the execution of rm contingent on the success of cd:

cd $dir_name && rm *

but still leaves open the possibility that the variable, dir_name, is unset or empty, which would result in the files in the user’s home directory being deleted.

[[ -d $dir_name ]] && cd $dir_name && rm *
  1. 是否为一个真正存在的目录
  2. cd是否成功执行

验证输入

[[ $REPLY =~ ^[0-3]$ ]]

测试

root@ubuntu:/code/test# touch 123
root@ubuntu:/code/test# ls
123
root@ubuntu:/code/test# touch 345
root@ubuntu:/code/test# echo $(rm *)

root@ubuntu:/code/test# ls
root@ubuntu:/code/test# touch 234
root@ubuntu:/code/test# touch 345
root@ubuntu:/code/test# echo rm *
rm 234 345
root@ubuntu:/code/test# echo *
234 345
root@ubun
  1. 用参数展开 会先执行,那就完蛋了
if [[ -d $dir_name ]]; then
    if cd $dir_name; then
        echo rm * # TESTING
    else
        echo "cannot cd to '$dir_name'" >&2
        exit 1
    fi
else
    echo "no such directory: '$dir_name'" >&2
    exit 1
fi
exit # TESTING

测试案例

  1. 存在
  2. 不存在

调试

  1. 添加注释
  2. tracing
    添加提示性信息,不缩进,方便删除
    在脚本第一行语句添加-x选项,能追踪整个脚本
#!/bin/bash -x
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
    echo "Number is equal to 1."
else
    echo "Number is not equal to 1."
fi
root@ubuntu:/code# ./trouble.sh 
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.
#The leading plus signs indicate the display of the trace to distinguish them from lines of regular output.

如果想在tracing时显示行号 export PS4=$LINENO +(不work)
通过set -x set +x部分跟踪

#!/bin/bash
# trouble: script to demonstrate common errors
number=1
set -x # Turn on tracing
if [ $number = 1 ]; then
    echo "Number is equal to 1."
else
    echo "Number is not equal to 1."
fi
set +x # Turn off tracing
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.
+ set +x

执行时检查数值

echo "number=$number" # DEBUG

流程控制

case

case word in
    [pattern [| pattern]...) commands ;;]...
esac
delay=3
while true; do
	clear
	echo "
	please select:
	1. display system information
	2. display disk space
	3. display home space utilization
	0. exit 
	"
	read -p "enter selection [0..3] ->"
	case $REPLY in
		0 ) echo "programme terminates"
			exit
			;;
		1 ) echo "hostname is $HOSTNAME"
			uptime
			sleep $delay
			continue
			;;
		2 ) df -h 
			sleep $delay
			continue
			;;
		3 ) du -sh $HOME
			sleep $delay
			continue
			;;
		* ) echo "invalid entry"
			sleep $delay
			continue
			;;
	esac
done

模式

The patterns used by case are the same as those used by pathname expansion. Patterns are terminated with a “)” character. Here are some valid patterns: pattern的规则与路径展开一样
在这里插入图片描述

#!/bin/bash
read -p "enter word > "
case $REPLY in
    [[:alpha:]])        echo "is a single alphabetic character." ;;
    [ABC][0-9])         echo "is A, B, or C followed by a digit." ;;
    ???)                echo "is three characters long." ;;
    *.txt)              echo "is a word ending in '.txt'" ;;
    *)                  echo "is something else." ;;
esac

It is also possible to combine multiple patterns using the vertical bar character as a separator. This creates an “or” conditional pattern. This is useful for such things as handling both upper- and lowercase characters.

#!/bin/bash
# case-menu: a menu driven system information program
clear
echo "
Please Select:
A. Display System Information
B. Display Disk Space
C. Display Home Space Utilization
Q. Quit
"
read -p "Enter selection [A, B, C or Q] > "
case $REPLY in
q|Q) echo "Program terminated."
     exit
     ;;
a|A) echo "Hostname: $HOSTNAME"
     uptime
     ;;
b|B) df -h
     ;;
c|C) if [[ $(id -u) -eq 0 ]]; then
         echo "Home Space Utilization (All Users)"
         du -sh /home/*
     else
         echo "Home Space Utilization ($USER)"
         du -sh $HOME
     fi
     ;;
*)   echo "Invalid entry" >&2
     exit 1
     ;;
esac

-n nchars return after reading NCHARS characters rather than waiting
for a newline, but honor a delimiter if fewer than
NCHARS characters are read before the delimiter
键入一个字符后立刻停止,不需要回车

#!/bin/bash
# case4-2: test a character
read -n 1 -p "Type a character > "
echo
case $REPLY in
    [[:upper:]])    echo "'$REPLY' is upper case." ;;&
    [[:lower:]])    echo "'$REPLY' is lower case." ;;&
    [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
    [[:digit:]])    echo "'$REPLY' is a digit." ;;&
    [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
    [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
    [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
    [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

位置参数

accept and process command line options and arguments

The shell provides a set of variables called positional parameters that contain the individ- ual words on the command line. The variables are named 0 through 9. They can be demonstrated this way:

root@ubuntu:/code# ./trouble.sh a v c d e f tg f as ef g we r gfgdfg ad

$0 = ./trouble.sh #$0 will always contain the first item appearing on the command line, which is the pathname of the program being executed. 
$1 = a
$2 = v
$3 = c
$4 = d
$5 = e
$6 = f
$7 = tg
$8 = f
$9 = as

$10_wo= $10 如果没有{},那么$1不读取0

root@ubuntu:/code# ./trouble.sh a v c d e f tg f as ef g we r gfgdfg ad

$0 = ./trouble.sh
$1 = a
$2 = v
$3 = c
$4 = d
$5 = e
$6 = f
$7 = tg
$8 = f
$9 = as
$10 = ef  #${10}
$10_wo= a0

yields the number of arguments on the com-mand line:

Number of arguments: $# 
#!/bin/bash
# posit-param2: script to display all arguments
count=1
while [[ $# -gt 0 ]]; do
    echo "Argument $count = $1"
    count=$((count + 1))
    shift
done
root@ubuntu:/code# ./trouble.sh a v c d e f tg f as ef g we r gfgdfg ad
arguments 1=a
arguments 2=v
arguments 3=c
arguments 4=d
arguments 5=e
arguments 6=f
arguments 7=tg
arguments 8=f
arguments 9=as
arguments 10=ef
arguments 11=g
arguments 12=we
arguments 13=r
arguments 14=gfgdfg
arguments 15=ad

  1. Each time shift is executed, the value of $2 is moved to $1, the value of $3 is moved to $2 and so on. The value of $# is also reduced by one. $2变成$1且$#减一
#!/bin/bash
# file_info: simple file information program
PROGNAME=$(basename $0)
if [[ -e $1 ]]; then
    echo -e "\nFile Type:"
    file $1
    echo -e "\nFile Status:"
    stat $1
else
    echo "$PROGNAME: usage: $PROGNAME file" >&2
    exit 1
fi
  1. PROGNAME 变量。它的值就是 basename $0 命令的执行结果。这个 basename 命令清除 一个路径名的开头部分,只留下一个文件的基本名称

shell函数中使用位置参数

file_info () {
  # file_info: function to display file information
  if [[ -e $1 ]]; then
      echo -e "\nFile Type:"
      file $1
      echo -e "\nFile Status:"
      stat $1
  else
      echo "$FUNCNAME: usage: $FUNCNAME file" >&2
      return 1
  fi
}
  1. $1不能直接传进shell函数,正确调用方式file_info $1
  2. $FUNCNAME 自动跟踪函数名字

处理集体位置参数

在这里插入图片描述

#!/bin/bash
# posit-params3 : script to demonstrate $* and $@
print_params () {
    echo "\$1 = $1"
    echo "\$2 = $2"
    echo "\$3 = $3"
    echo "\$4 = $4"
}
pass_params () {
    echo -e "\n" '$* :';      print_params   $*
    echo -e "\n" '"$*" :';    print_params   "$*"
    echo -e "\n" '$@ :';      print_params   $@
    echo -e "\n" '"$@" :';    print_params   "$@"
}
pass_params "word" "words with spaces"

 $* :
$1 = word
$2 = words
$3 = with
$4 = spaces

 "$*" :
$1 = word words with spaces
$2 = 
$3 = 
$4 = 

 $@ :
$1 = word
$2 = words
$3 = with
$4 = spaces

 "$@" :
$1 = word
$2 = words with spaces
$3 = 
$4 = 

With our arguments, both $* and @ p r o d u c e a f o u r w o r d r e s u l t : w h i c h m a t c h e s o u r a c t u a l i n t e n t . T h e l e s s o n t o t a k e f r o m t h i s i s t h a t e v e n t h o u g h t h e s h e l l p r o v i d e s f o u r d i f f e r e n t w a y s o f g e t t i n g t h e l i s t o f p o s i t i o n a l p a r a m e t e r s , “ @ produce a four word result: which matches our actual intent. The lesson to take from this is that even though the shell provides four different ways of getting the list of positional parameters, “ @produceafourwordresult:whichmatchesouractualintent.Thelessontotakefromthisisthateventhoughtheshellprovidesfourdifferentwaysofgettingthelistofpositionalparameters,@” is by far the most useful for most situations, because it preserves the integrity of each positional parameter.

#!/bin/bash
# Program to output a system information page
PROGNAME=$(basename $0)

function write_html_page()
{
cat << _EOF_
<HTML>
    <HEAD>
          <TITLE>$title</TITLE>
    </HEAD>
    <BODY>
          <H1>$title</H1>
	  <p>$time_stamp</p>
	  $(report_uptime)
	  $(report_disk_space)
	  $(report_home_space)
    </BODY>
</HTML>
_EOF_
return 
}


function usage()
{
  echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
  return
}

interactive=
filename=
#while the length of the first parameter > 0
while [[ -n "$1" ]]; do
  case $1 in
    -f | --file ) 
      shift
      filename=$1
      ;;
    -i | --interactive) 
      interactive=1
      ;;
    -h | --help)
      usage
      exit
      ;;
    * )
      usage >&2
      exit 1
      ;;
  esac
  shift
done
# interactive mode
if [[ -n $interactive ]]; then
  while true; do
    read -p "enter a filename for output" filename
    if [[ -e "$filename" ]]; then
      read -p "file exist. overwrite(y/n/q) > "
      case $REPLY in
        y | Y)
          break;
          ;;
        n | N)
          continue
          ;;
        q|Q)
          echo "Program terminates"
          exit
          ;;
        *)
          continue
          ;;
      esac
    elif [[ -z filename ]]; then #字符串的长度是零
      continue
    else
      break
    fi
  done
fi

if [[ -n $filename ]]; then
  if touch $filename && [[ -f $filename ]]; then
    write_html_page > $filename
  else
    echo "$PROGNAME: Cannot write file '$filename'" >&2
  fi
else
  write_html_page
fi
  1. 需要用的变量可以filename=先空着
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值