第三十三章 杂项

33.1 交互式和非交互式的shell和脚本

交互式的shell在tty终端从用户的输入中读取命令。另一方面,shell能在启动时读取启动文件,显示一个提示符并默认激活作业控制。用户能交互地使用shell。

运行脚本的shell一般都是非交互的shell。但脚本仍然可以存取他拥有的终端。脚本里甚至可以仿效成可交互的shell。

#!/bin/bash
#
MY_PROMPT='$ 'while :
doecho -n "$MY_PROMPT"read lineeval "$line"
done
exit 0

初始化和启动脚本是非交互式的,因为它们必须不需要人为地干预地运行。许多管理和系统维护脚本也同样是非交互式的。不多变的重复性的任务可以自动地由非交互式脚本完成。

非交互式的脚本可以在后台运行,但交互脚本在后台运行则会被挂起,等待永远不会到达的输入。解决这个难点的办法可以写预料这种情况的脚本或是内嵌 here document 的脚本来获取脚本期望的输入,这样就可作为后台任务运行了。在最简单的情况,重定向一个文件给一个 read 语句提供输入(read variable < file)。这就可能适应交互和非交互的工作环境下都能达成脚本运行的目的。

如果脚本需要测试当前是否运行在交互 shell 中,一个简单的办法是找一下是否有提示符变量,即$PS1 是否设置了。(如果脚本需要用户输入数据,则脚本会显示一个提示符.)

if [ -z $PS1 ];then   #没有提示符?
#非交互式...
else
#交互式...
fi

另一个办法是脚本可以测试是否在变量$-中出现了选项"i"。

case $- in
*i*) ...;;   #交互式
*) ...;;   #非交互式

注意:脚本可以使用-i 选项强制在交互模式下运行或脚本头用#!/bin/bash -i。注意这样可能会引起脚本古怪的行为或当没有错误出现时也会显示错误信息。

33.2 Shell包装

包装脚本是指嵌有一个系统命令和程序的脚本,也保存了一组传给该命令的参数。包装脚本是原来很复杂的命令行简单化。这对sed和awk特别有用。

sed和awk命令一般从命令行上以sed -e 'commands’和awk 'commands’来调用。把sed和awk的命令嵌入到Bash脚本里使调用变得更简单,并且也可以多次使用。也可以综合的利用sed和awk的功能。例如管道(piping)连接 sed 命令的输出到 awk 命令中。保存为可执行的文件,你可以用脚本编写的或修改的调用格式多次的调用它,而不必在命令行上重复键入复杂的命令行。

Example 33-1 shell包装

#!/bin/bash
#
#这个是一个把文件中的空行删除的简单脚本
#没有参数#
E_NOARGS=65
if [ -z "$1" ];thenecho "Usage: `basename $0` target-file"exit $E_NOARGS
fised -e /^$/d "$1"
exit 0

Example 33-2 稍微复杂一些的shell包装

#!/bin/bash
#
ARGS=3   #脚本要求三个参数.
E_BADARGS=65   #传递了错误的参数个数给脚本.if [ $# -ne "$ARGS" ];thenecho "Usage: `basename $0` old-pattern new-pattern filename"exit $E_BADARGS
fiold_pattern=$1
new_pattern=$2if [ -f "$3" ];thenfile_name=$3
elseecho "File \"$3\" does not exist."exit $E_BADARGS
fi#这里是实现功能的代码
sed -e "s/$old_pattern/$new_pattern/g" $file_name
exit 0

Example 33-3 写到日志文件的shell包装

#!/bin/bash
#
#普通的 shell 包装,执行一个操作并记录在日志里#需要设置下面的两个变量.
OPERATION=
#可以是一个复杂的命令链,例如 awk 脚本或是管道 . . .
LOGFILE=
#不管怎么样,命令行参数还是要提供给操作的.
OPTIONS="$@"#记录操作
echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
#现在执行操作
exec $OPERATION "$@""

Example 33-4 包装awk的脚本

#!/bin/bash
#
#打印 ASCII 码的字符表.
START=33
END=125echo "Decimal   Hex     Character"   #表头
echo "-----     ---     ----------"for ((i=START;i<=END;i++))
doecho $i | awk '{printf(" %3d      %2x           %c\n",$1,$1,$1)}'
done
exit 0

Example 33-5 另一个包装awk的脚本

#!/bin/bash
#
#给目标文件增加一列由数字指定的列.ARGS=2
E_WRONGARGS=65if [ $# -ne "$ARGS" ];thenecho "Usage: `basename $0` filename column-number"exit $E_WRONGARGS
fifilename=$1
column_number=$2
#
#传递 shell 变量给脚本的 awk 部分需要一点技巧.
#  方法之一是在 awk 脚本中使用强引用来引起 bash 脚本的变量
#  $'$BASH_SCRIPT_VAR'awk '
{total += $'"$column_number"'
}
END{print total
}' "$filename"#把 shell 变量传递给 awk 变量可能是不安全的,
#因此 Stephane Chazelas 提出了下面另外一种方法:
# awk -v column_number="$column_number" '
# {total += $column_number
# }
# END{
#    print total
#}' "$filename"
exit 0

Perl 兼有 sed 和 awk 的能力,并且具有 C 的一个很大的子集。它是标准的并支持面向对象编程的方方面面,甚至是很琐碎的东西。 短的 Perl 脚本也可以嵌入到 shell 脚本中去,以至于有些人宣称 Perl 能够完全地代替 shell 编程。

Example 33-6 把Perl嵌入Bash脚本

#!/bin/bash
#
echo "This precedes the embedded Perl script within \"$0\"."
echo "======================================================"
perl -e 'print "This is an embedded Perl script.\n";'
#sed 脚本, Perl 也使用"-e"选项.
echo "======================================================"
1 echo "However, the script may also contain shell and system commands."
exit 0

把 Bash 脚本和 Perl 脚本放在同一个文件是可能的。依赖于脚本如何被调用,要么是 Bash 部分被执行,要么是 Perl 部分被执行。

Example 33-7 Bash和Perl脚本联合使用

#!/bin/bash
#
echo "Greetings from the Bash part of the script."
#...
exit 0
#脚本的 Bash 部分结束.
#==================================================#!/usr/bin/perl
# 脚本的这个部分必须用-x 选项来调用.
print "Greetings from the Perl part of the script.\n"
#下面可以有更多的 Perl 命令.
#...
#脚本的 Perl 部分结束.下面是执行结果
(base) [root@zhhs-mail shell]# sh test.sh
Greetings from the Bash part of the script.
(base) [root@zhhs-mail shell]# perl -x test.sh
Greetings from the Perl part of the script.

33.3 测试和比较:另一种方法

对于测试,[[ ]]结构可能比[ ]更合适。同样地,算术比较可能用(( ))结构更有用。

a=8
#下面所有的比较是等价的
test "$a" -lt 16 && echo "yes,$a < 16"
/bin/test "$a" -lt 16 && echo "yes,$a < 16"
[ "$a" -lt 16 ] && echo "yes,$a < 16"
[[ $a -lt 16 ]] && echo "yes,$a < 16"   ## 在[[ ]]和(( ))中不必用引号引起变量
((a < 16)) && echo "yes,$a < 16"city="New York"
#同样,下面的所有比较都是等价的.
test "$city" \< Paris && echo "Yes, Paris is greater than $city"
/bin/test "$city" \< Paris && echo "Yes, Paris is greater than $city"
[ "$city" \< Paris ] && echo "Yes, Paris is greater than $city"
[[ $city < Paris ]] && echo "Yes, Paris is greater than $city"   # 不需要用引号引起$city.

33.4 递归

脚本能递归地调用自己本身。

Example 33-8 递归调用自己本身的(没用)脚本

#!/bin/bash
#
RANGE=10
MAXVAL=9i=$RANDOM
let "i %= $RANGE"   #产生一个从 0 到 $RANGE - 1 之间的随机数.if [ "$i" -lt "$MAXVAL" ];thenecho "i = $i"./$0   #脚本递归地调用再生成一个和自己一样的实例.
fi
#每个子脚本做的事都一样,直到产生的变量 $i 和变量 $MAXVAL 相等.
exit 0

Example 33-9 递归调用自己本身的(有用)脚本

#!/bin/bash
#
MINARGS=1   #脚本需要至少一个参数
DATAFILE=./phonebook   #在当前目录下名为"phonebook"的数据文件必须存在PROGNAME=$0
E_NOARGS=70   #没有参数的错误值if [ $# -lt $MINARGS ];thenecho "Usage: "$PROGNAME" data"exit $E_NOARGS
fiif [ $# -eq $MINARGS ];thengrep $1 "$DATAFILE"
else( shift; "$PROGNAME" $* ) | grep $1
fi
exit 0#-------------------------
"phonebook"文件的例子:
John Doe 1555 Main St., Baltimore, MD 21228 (410) 222-3333
Mary Moe 9899 Jones Blvd., Warren, NH 03787 (603) 898-3232
Richard Roe 856 E. 7th St., New York, NY 10009 (212) 333-4567
Sam Roe 956 E. 8th St., New York, NY 10009 (212) 444-5678
Zoe Zenobia 4481 N. Baker St., San Francisco, SF 94338 (415) 501-1631
#--------------------------

33.5 彩色脚本

ANSI定义了屏幕属性的转义序列集合,例如:粗体文本,背景和前景颜色。DOS 批处理文件(batch files) 一般使用 ANSI 的转义代码来控制色彩输出,Bash 脚本也是这么做的。

Example 33-11 一个 “彩色的” 地址资料库

#!/bin/bash
#
clear
echo -n "                "
echo -e '\E[37;44m'"\033[1mContact List\033[0m"   #白色为前景色,蓝色为背景色
echo
echo
echo -e "\033[1mChoose one of the following persons:\033[0m"   #粗体tput sgr0
echo "(只输入姓名的第一个字母.)"
echo
echo -en '\E[47;34m'"\033[1mE\033[0m"   #蓝色
tput sgr0   #把色彩设置为"常规"
echo "vans, Roland"
echo -en '\E[47;35m'"\033[1mJ\033[0m"   #红紫色
tput sgr0
echo "ones, Mildred"
echo -en '\E[47;32m'"\033[1mS\033[0m"   #绿色
tput sgr0
echo "mith, Julie"
echo -en '\E[47;31m'"\033[1mZ\033[0m"   #红色
tput sgr0
echo "ane, Morris"
echoread personcase "$person" in
"E" | "e" )
# 接受大小写的输入.
echo
echo "Roland Evans"
echo "4321 Floppy Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734-9874"
echo "(303) 734-9892 fax"
echo "revans@zzy.net"
echo "Business partner & old friend"
;;
"J" | "j" )
echo
echo "Mildred Jones"
echo "249 E. 7th St., Apt. 19"
echo "New York, NY 10009"
echo "(212) 533-2814"
echo "(212) 533-9972 fax"
echo "milliej@loisaida.com"
echo "Girlfriend"
echo "Birthday: Feb. 11"
;;
*)
echo
echo "Not yet in database."
;;
esac
tput sgr0
echo
exit 0

Example 33-12 画盒子(复制粘贴的)

#!/bin/bash
# 用 ASCII 字符画一个盒子.### draw_box 函数的注释 ###
# "draw_box" 函数使用户可以在终端上画一个盒子.
#
# 用法: draw_box ROW COLUMN HEIGHT WIDTH [COLOR]
# ROW 和 COLUMN 定位要画的盒子的左上角.
#
# ROW 和 COLUMN 必须要大于 0 且小于目前终端的尺寸.
#
# HEIGHT 是盒子的行数,必须 > 0.
# HEIGHT + ROW 必须 <= 终端的高度.
# WIDTH 是盒子的列数,必须 > 0.
# WIDTH + COLUMN 必须 <= 终端的宽度.
#
# 例如: 如果你当前终端的尺寸是 20x80,
# draw_box 2 3 10 45 是合法的
# draw_box 2 3 19 45 的 HEIGHT 值是错的 (19+2 > 20)
# draw_box 2 3 18 78 的 WIDTH 值是错的 (78+3 > 80)
#
# COLOR 是盒子边框的颜色.
# 它是第 5 个参数,并且它是可选的.
# 0=黑色 1=红色 2=绿色 3=棕褐色 4=蓝色 5=紫色 6=青色 7=白色.
# 如果你传给这个函数错的参数,
#+ 它就会以代码 65 退出,
#+ 没有其他的信息打印到标准出错上.
#
# 在画盒子之前要清屏.
# 函数内不包含有清屏命令.
# 这使用户可以画多个盒子,甚至叠接多个盒子.### draw_box 函数注释结束 ###########################################################
draw_box(){
#=============#
HORZ="-"
VERT="|"
CORNER_CHAR="+"MINARGS=4
E_BADARGS=65
#=============#
if [ $# -lt "$MINARGS" ]; then # 如果参数小于 4,退出.exit $E_BADARGS
fi# 搜寻参数中的非数字的字符.
if echo $@ | tr -d [:blank:] | tr -d [:digit:] | grep . &> /dev/null; thenexit $E_BADARGS
fiBOX_HEIGHT=`expr $3 - 1` # -1 是需要的,因为因为边角的"+"是高和宽共有的部分.
BOX_WIDTH=`expr $4 - 1` #
T_ROWS=`tput lines` # 定义当前终端长和宽的尺寸,
T_COLS=`tput cols` #if [ $1 -lt 1 ] || [ $1 -gt $T_ROWS ]; then # 如果参数是数字就开始检查有效性.exit $E_BADARGS #
fi
if [ $2 -lt 1 ] || [ $2 -gt $T_COLS ]; thenexit $E_BADARGS
fi
if [ `expr $1 + $BOX_HEIGHT + 1` -gt $T_ROWS ]; thenexit $E_BADARGS
fi
if [ `expr $2 + $BOX_WIDTH + 1` -gt $T_COLS ]; thenexit $E_BADARGS
fi
if [ $3 -lt 1 ] || [ $4 -lt 1 ]; thenexit $E_BADARGS
fi # 参数检查完毕.plot_char(){ # 函数内的函数.
echo -e "\E[${1};${2}H"$3
}echo -ne "\E[3${5}m" # 如果传递了盒子边框颜色参数,则设置它.# start drawing the boxcount=1 # 用 plot_char 函数画垂直线
for (( r=$1; count<=$BOX_HEIGHT; r++)); doplot_char $r $2 $VERTlet count=count+1
donecount=1
c=`expr $2 + $BOX_WIDTH`
for (( r=$1; count<=$BOX_HEIGHT; r++)); doplot_char $r $c $VERTlet count=count+1
donecount=1 # 用 plot_char 函数画水平线
for (( c=$2; count<=$BOX_WIDTH; c++)); do #plot_char $1 $c $HORZlet count=count+1
donecount=1
r=`expr $1 + $BOX_HEIGHT`
for (( c=$2; count<=$BOX_WIDTH; c++)); doplot_char $r $c $HORZlet count=count+1
doneplot_char $1 $2 $CORNER_CHAR # 画盒子的角.
plot_char $1 `expr $2 + $BOX_WIDTH` +
plot_char `expr $1 + $BOX_HEIGHT` $2 +
plot_char `expr $1 + $BOX_HEIGHT` `expr $2 + $BOX_WIDTH` +echo -ne "\E[0m" # 恢复最初的颜色.P_ROWS=`expr $T_ROWS - 1` # 在终端的底部打印提示符.echo -e "\E[${P_ROWS};1H"
}# 现在, 让我们来画一个盒子.
clear # 清屏.
R=2 # 行
C=3 # 列
H=10 # 高
W=45 # 宽
col=1 # 颜色(红)
draw_box $R $C $H $W $col # 画盒子.exit 0

最简单也可能是最有用的 ANSI 转义序列是加粗文本,\033[1m … \033[0m。\033 触发转义序列,而 “[1” 启用加粗属性, 而"[0" 表示切换回禁用加粗状态。"m"则表示终止一个转义序列。

echo -e "\033[1mThis is bold text.\033[0m"

一种相似的转义序列可切换下划线效果 (在 rxvt 和 aterm 上)。

echo -e "\033[4mThis is underlined text.\033[0m"

注意: echo 使用-e 选项可以启用转义序列。

其他的转义序列可用于更改文本或/和背景色彩。

echo -e '\E[34;47mThis prints in blue.'; tput sgr0
echo -e '\E[33;44m'"yellow text on blue background"; tput sgr0
echo -e '\E[1;33;44m'"BOLD yellow text on blue background"; tput sgr0

注意:通常为淡色的前景色文本设置粗体效果是较好的。

tput sgr0把终端设置恢复为原样。如果省略这一句会使后续在该终端的输出仍为蓝色。

可以在有色的背景上用下面的模板写有色彩的文本

echo -e '\E[COLOR1;COLOR2mSome text goes here.'

“\E[” 开始转义序列。分号分隔的数值"COLOR1" 和 “COLOR2” 指定前景色和背景色,数值和色彩的对应参见下面的表格。(数值的顺序不是有关系的,因为前景色和背景色数值都落在不重叠的范围里。) "m"终止该转义序列,然后文本以结束的转义指定的属性显示。

也要注意到用单引号引用了 echo -e 后面的余下命令序列。

下表的数值是在 rxvt 终端运行的结果。具体效果可能在其他的各种终端上不一样。

色彩 前景色 背景色
30 40
31 41
绿 32 42
33 43
34 44
洋红 35 45
36 46
37 47

Example 33-13 显示彩色文本

#!/bin/bash
#
#用彩色来显示文本
black='\E[30;47m'
red='\E[31;47m'
green='\E[32;47m'
yellow='\E[33;47m'
blue='\E[34;47m'
magenta='\E[35;47m'
cyan='\E[36;47m'
white='\E[37;47m' alias Reset="tput sgr0"   # 把文本属性重设回原来没有清屏前的cecho()   #参数 $1 = 要显示的信息,参数 $2 = 颜色
{local default_msg="No message passed."message=${1:-$default_msg} # 默认的信息.color=${2:-$black} # 如果没有指定,默认使用黑色.echo -e "$color"echo "$message"Resetreturn
}
cecho "Feeling blue..." $blue
cecho "Magenta looks more like purple." $magenta
cecho "Green with envy." $green
cecho "Seeing red?" $red
cecho "Cyan, more familiarly known as aqua." $cyan
cecho "No color passed (defaults to black)."
# 缺失 $color (色彩)参数.
cecho "\"Empty\" color passed (defaults to black)." ""
# 空的 $color (色彩)参数.
cecho
# $message(信息) 和 $color (色彩)参数都缺失.
cecho "" ""
# 空的 $message (信息)和 $color (色彩)参数.
exit 0

Example 33-14 "赛马"游戏(复制粘贴的)

#!/bin/bash
# horserace.sh: 非常简单的赛马模拟.
# 作者: Stefano Palmeri
# 已取得使用许可.#########################################################
#
# 脚本目的:
# 使用转义字符和终端颜色.
#
# 练习:
# 编辑脚本使其更具有随机性,
#+ 设置一个假的赌场 . . .
# 嗯 . . . 嗯 . . . 这个开始使我想起了一部电影 . . .
#
# 脚本给每匹马一个随机的障碍.
# 不均等会以障碍来计算
#+ 并且用一种欧洲风格表达出来.
# 例如: 机率(odds)=3.75 意味着如果你押 1 美元赢,
#+ 你可以赢得 3.75 美元.
#
# 脚本已经在 GNU/Linux 操作系统上测试过 OS,
#+ 测试终端有 xterm 和 rxvt, 及 konsole.
# 测试机器有 AMD 900 MHz 的处理器,
#+ 平均比赛时间是 75 秒.
# 在更快的计算机上比赛时间应该会更低.
# 所以, 如果你想有更多的悬念,重设 USLEEP_ARG 变量的值.
#
# 由 Stefano Palmeri 编写.############################################################
####E_RUNERR=65# 检查 md5sum 和 bc 是不是安装了.
if ! which bc &> /dev/null; then
echo bc is not installed.
echo "Can\'t run . . . "
exit $E_RUNERR
fi
if ! which md5sum &> /dev/null; then
echo md5sum is not installed.
echo "Can\'t run . . . "
exit $E_RUNERR
fi# 更改下面的变量值可以使脚本执行的更慢.
# 它会作为 usleep 的参数 (man usleep)
#+ 并且它的单位是微秒 (500000 微秒 = 半秒).
USLEEP_ARG=0# 如果脚本接收到 ctrl-c 中断,清除临时目录, 恢复终端光标和颜色
#
trap 'echo -en "\E[?25h"; echo -en "\E[0m"; stty echo;\
tput cup 20 0; rm -fr $HORSE_RACE_TMP_DIR' TERM EXIT
# 参考调试的章节了解'trap'的更多解释# 给脚本设置一个唯一(实际不是绝对唯一的)的临时目录名.
HORSE_RACE_TMP_DIR=$HOME/.horserace-`date +%s`-`head -c10 /dev/urandom |
md5sum | head -c30`# 创建临时目录,并切换到该目录下.
mkdir $HORSE_RACE_TMP_DIR
cd $HORSE_RACE_TMP_DIR# 这个函数把光标移动到行为 $1 列为 $2 然后打印 $3.
# 例如: "move_and_echo 5 10 linux" 等同于
#+ "tput cup 4 9; echo linux", 但是用一个命令代替了两个.
# 注: "tput cup" 表示在终端左上角的 0 0 位置,
#+ echo 是在终端的左上角的 1 1 位置.
move_and_echo() {
echo -ne "\E[${1};${2}H""$3"
}# 产生 1-9 之间伪随机数的函数.
random_1_9 () {
head -c10 /dev/urandom | md5sum | tr -d [a-z] | tr -d 0 | cut -c1
}# 画马时模拟运动的两个函数.
draw_horse_one() {
echo -n " "//$MOVE_HORSE//
}
draw_horse_two(){
echo -n " "\\\\$MOVE_HORSE\\\\
}# 取得当前的终端尺寸.
N_COLS=`tput cols`
N_LINES=`tput lines`# 至少需要 20-行 X 80-列 的终端尺寸. 检查一下.
if [ $N_COLS -lt 80 ] || [ $N_LINES -lt 20 ]; then
echo "`basename $0` needs a 80-cols X 20-lines terminal."
echo "Your terminal is ${N_COLS}-cols X ${N_LINES}-lines."
exit $E_RUNERR
fi# 开始画赛场.# 需要一个 80 个字符的字符串,看下面的.
BLANK80=`seq -s "" 100 | head -c80`clear# 把前景和背景颜色设置成白色的.
echo -ne '\E[37;47m'# 把光标移到终端的左上角.
tput cup 0 0# 画六条白线.
for n in `seq 5`; do
echo $BLANK80 # 线是用 80 个字符组成的字符串.
done# 把前景色设置成黑色.
echo -ne '\E[30m'move_and_echo 3 1 "START 1"
move_and_echo 3 75 FINISH
move_and_echo 1 5 "|"
move_and_echo 1 80 "|"
move_and_echo 2 5 "|"
move_and_echo 2 80 "|"
move_and_echo 4 5 "| 2"
move_and_echo 4 80 "|"
move_and_echo 5 5 "V 3"
move_and_echo 5 80 "V"# 把前景色设置成红色.
echo -ne '\E[31m'# 一些 ASCII 艺术.
move_and_echo 1 8 "..@@@..@@@@@...@@@@@.@...@..@@@@..."
move_and_echo 2 8 ".@...@...@.......@...@...@.@......."
move_and_echo 3 8 ".@@@@@...@.......@...@@@@@.@@@@...."
move_and_echo 4 8 ".@...@...@.......@...@...@.@......."
move_and_echo 5 8 ".@...@...@.......@...@...@..@@@@..."
move_and_echo 1 43 "@@@@...@@@...@@@@..@@@@..@@@@."
move_and_echo 2 43 "@...@.@...@.@.....@.....@....."
move_and_echo 3 43 "@@@@..@@@@@.@.....@@@@...@@@.."
move_and_echo 4 43 "@..@..@...@.@.....@.........@."
move_and_echo 5 43 "@...@.@...@..@@@@..@@@@.@@@@.."# 把前景和背景颜色设为绿色.
echo -ne '\E[32;42m'# 画 11 行绿线.
tput cup 5 0
for n in `seq 11`; do
echo $BLANK80
done# 把前景色设为黑色.
echo -ne '\E[30m'
tput cup 5 0# 画栅栏.
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"tput cup 15 0
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"# 把前景和背景色设回白色.
echo -ne '\E[37;47m'# 画 3 条白线.
for n in `seq 3`; do
echo $BLANK80
done# 把前景色设为黑色.
echo -ne '\E[30m'# 创建 9 个文件来保存障碍物.
for n in `seq 10 7 68`; do
touch $n
done# 设置脚本要画的马的类型为第一种类型.
HORSE_TYPE=2# 为每匹马创建位置文件和机率文件.
#+ 在这些文件里保存了该匹马当前的位置,
#+ 类型和机率.
for HN in `seq 9`; do
touch horse_${HN}_position
touch odds_${HN}
echo \-1 > horse_${HN}_position
echo $HORSE_TYPE >> horse_${HN}_position
# 给马定义随机的障碍物.
HANDICAP=`random_1_9`
# 检查 random_1_9 函数是否返回了有效值.
while ! echo $HANDICAP | grep [1-9] &> /dev/null; do
HANDICAP=`random_1_9`
done
# 给马定义最后的障碍的位置.
LHP=`expr $HANDICAP \* 7 + 3`
for FILE in `seq 10 7 $LHP`; do
echo $HN >> $FILE
done# 计算机率.
case $HANDICAP in
1) ODDS=`echo $HANDICAP \* 0.25 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
2 | 3) ODDS=`echo $HANDICAP \* 0.40 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
4 | 5 | 6) ODDS=`echo $HANDICAP \* 0.55 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
7 | 8) ODDS=`echo $HANDICAP \* 0.75 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
9) ODDS=`echo $HANDICAP \* 0.90 + 1.25 | bc`
echo $ODDS > odds_${HN}
esacdone# 打印机率.
print_odds() {
tput cup 6 0
echo -ne '\E[30;42m'
for HN in `seq 9`; do
echo "#$HN odds->" `cat odds_${HN}`
done
}# 在起跑线上画马.
draw_horses() {
tput cup 6 0
echo -ne '\E[30;42m'
for HN in `seq 9`; do
echo /\\$HN/\\" "
done
} print_oddsecho -ne '\E[47m'
# 等待回车按键开始赛马.
# 转义序列'\E[?25l'禁显了光标.
tput cup 17 0
echo -e '\E[?25l'Press [enter] key to start the race...
read -s# 禁用了终端的常规显示功能.
# 这避免了赛跑时不小心按了按键键入显示字符而弄乱了屏幕.
#
stty -echo# --------------------------------------------------------
# 开始赛跑.draw_horses
echo -ne '\E[37;47m'
move_and_echo 18 1 $BLANK80
echo -ne '\E[30m'
move_and_echo 18 1 Starting...
sleep 1# 设置终点线的列数.
WINNING_POS=74# 记录赛跑开始的时间.
START_TIME=`date +%s`# COL 是由下面的"while"结构使用的.
COL=0while [ $COL -lt $WINNING_POS ]; doMOVE_HORSE=0# 检查 random_1_9 函数是否返回了有效值.
while ! echo $MOVE_HORSE | grep [1-9] &> /dev/null; do
MOVE_HORSE=`random_1_9`
done# 取得随机取得的马的类型和当前位置.
HORSE_TYPE=`cat horse_${MOVE_HORSE}_position | tail -1`
COL=$(expr `cat horse_${MOVE_HORSE}_position | head -1`)ADD_POS=1
# 检查当前的位置是否是障碍物的位置.
if seq 10 7 68 | grep -w $COL &> /dev/null; then
if grep -w $MOVE_HORSE $COL &> /dev/null; then
ADD_POS=0
grep -v -w $MOVE_HORSE $COL > ${COL}_new
rm -f $COL
mv -f ${COL}_new $COL
else ADD_POS=1
fi
else ADD_POS=1
fi
COL=`expr $COL + $ADD_POS`
echo $COL > horse_${MOVE_HORSE}_position # 保存新位置.# 选择要画的马的类型.
case $HORSE_TYPE in
1) HORSE_TYPE=2; DRAW_HORSE=draw_horse_two
;;
2) HORSE_TYPE=1; DRAW_HORSE=draw_horse_one
esac
echo $HORSE_TYPE >> horse_${MOVE_HORSE}_position # 保存当前类型.# 把前景色设为黑,背景色设为绿.
echo -ne '\E[30;42m'# 把光标位置移到新的马的位置.
tput cup `expr $MOVE_HORSE + 5` `cat horse_${MOVE_HORSE}_position |
head -1`# 画马.
$DRAW_HORSE
usleep $USLEEP_ARG# 当所有的马都越过 15 行的之后,再次打印机率.
touch fieldline15
if [ $COL = 15 ]; then
echo $MOVE_HORSE >> fieldline15
fi
if [ `wc -l fieldline15 | cut -f1 -d " "` = 9 ]; then
print_odds
: > fieldline15
fi# 取得领头的马.
HIGHEST_POS=`cat *position | sort -n | tail -1`# 把背景色重设为白色.
echo -ne '\E[47m'
tput cup 17 0
echo -n Current leader: `grep -w $HIGHEST_POS *position | cut -c7`"
"done# 取得赛马结束的时间.
FINISH_TIME=`date +%s`# 背景色设为绿色并且启用闪动的功能.
echo -ne '\E[30;42m'
echo -en '\E[5m'# 使获胜的马闪动.
tput cup `expr $MOVE_HORSE + 5` `cat horse_${MOVE_HORSE}_position | head -1`
$DRAW_HORSE# 禁用闪动文本.
echo -en '\E[25m'# 把前景和背景色设为白色.
echo -ne '\E[37;47m'
move_and_echo 18 1 $BLANK80# 前景色设为黑色.
echo -ne '\E[30m'# 闪动获胜的马.
tput cup 17 0
echo -e "\E[5mWINNER: $MOVE_HORSE\E[25m"" Odds: `cat odds_${MOVE_HORSE}`"\
" Race time: `expr $FINISH_TIME - $START_TIME` secs"# 恢复光标和最初的颜色.
echo -en "\E[?25h"
echo -en "\E[0m"# 恢复回显功能.
stty echo# 删除赛跑的临时文件.
rm -rf $HORSE_RACE_TMP_DIRtput cup 19 0exit 0

33.6 优化

大多数 shell 脚本处理不复杂的问题时会有很快的解决办法。正因为这样,优化脚本速度不是一个问题。考虑这样的情况,一个脚本处理很重要的任务,虽然它确实运行的很好很正确,但是处理速度太慢。用一种可编译的语言重写它可能不是非常好的选择。最简单的办法是重写使这个脚本效率低下的部分。这个代码优化的原理是否同样适用于效率低下的 shell 脚本?

检查脚本中的循环。反复执行操作的时间消耗增长非常的快。如果可能,可以从循环中删除时间消耗的操作。

优先使用内建(builtin)命令而不是系统命令。内建命令执行起来更快并且一般调用时不会产生新的子 shell。

避免不需要的命令,特别是管道(pipe)。

cat "$file" | grep "$word"grep "$word" "$file"
# 上面的命令行有同样的效果,但第二个运行的更有效率,因为它不产生新的子进程.

cat 命令似乎特别常在脚本中被滥用。
用 time 和 times 工具去了解计算花费的时间。考虑用 C 甚至是汇编重写关键的消耗时间的部分。

尝试最小化文件 I/O。Bash 在文件处理上不是特别地有效率,所以要考虑在脚本中使用更合适地工具来处理。比如说 awk 或 Perl。

采用结构化的思想来写脚本,使各个模块能够依据需要组织和合并起来。一些适用于高级语言的优化技术也可以用在脚本上,但有些技术,比如说循环优化几乎是不相关的。上面的讨论,依据经验来判断。

怎样优化减少执行时间的优秀脚本示例,请参考例子 12-42。

33.7 各种小技巧

为了记录在一个实际的会话期或多个会话期内运行的用户脚本,可以加下面的代码到每
个你想追踪记录的脚本里。这会记录下连续的脚本名记录和调用的次数。

# 添加(>>)下面几行到你想追踪记录的脚本末尾处.whoami>> $SAVE_FILE # 记录调用脚本的用户.
echo $0>> $SAVE_FILE # 记录脚本名.
date>> $SAVE_FILE # 记录日期和时间.
echo>> $SAVE_FILE # 空行作为分隔行.# 当然, SAVE_FILE 变量应在~/.bashrc 中定义并导出(export)。(变量值类似如 ~/.scripts-run)

操作符可以在文件尾添加内容。如果你想在文件头添加内容,那应该怎么办?

file=data.txt
title="***This is the title line of data text file***"echo $title | cat - $file >$file.new
# "cat -" 连接标准输出的内容和$file 的内容.
# 最后的结果就是生成了一个新文件,文件的头添加了 $title 的值,后跟$file 的内容.

脚本也可以像内嵌到另一个 shell 脚本的普通命令一样调用,如 Tcl 或 wish 脚本,甚至可以是 Makefile。它们可以作为外部
shell 命令用 C 语言的 system() 函数调用,例如system(“script_name”);。

把内嵌的 sed 或 awk 脚本的内容赋值给一个变量可以增加包装脚本(shell wrapper) 的可读性。参考 例子 A-1 和 例子
11-18。

把你最喜欢和最有用的定义和函数放在一些文件中。当需要的使用的时候,在脚本中使用 dot (.) 或 source
命令来"包含(include)"这些"库文件"的一个或多个。

#脚本库
#本文件没有"#!"开头。也没有正在做执行动作的代码#有用的变量定义
ROOT_UID=0 # Root 用户的 $UID 值是 0.
E_NOTROOT=101 # 非 root 用户出错代码.
MAXRETVAL=255 # 函数最大的的返回值(正值).
SUCCESS=0
FAILURE=-1#函数
Usage()   # "Usage:" 信息(即帮助信息).
{if [ -z "$1" ];thenmsg=filenameelsemsg=$@fiecho "Usage: `basename $0` "$msg""
}Check_if_root()   # 检查是不是 root 在运行脚本.
{if [ "$UID" -ne "$ROOT_UID" ];thenecho "Must be root to run this script."exit $E_NOTROOTfi
}CreateTempfileName()   # 创建一个"唯一"的临时文件.
{prefix=tempsuffix=`eval date +%s`Tempfilename=$prefix.$suffix
}isalph2()   # 测试字符串是不是都是字母组成的.
{[ $# -eq 1 ] || return $FAILUREcase $1 in*[!a-zA-Z]*|"") return $FAILURE;;*) return $SUCCESS;;esac
}abs()   # 绝对值.
{E_ARGERR=999999if [ -z "$1" ];then   # 要传递参数.return $E_ARGERR   # 返回错误.fiif [ "$1" -ge 0 ];then   # 如果非负的值,absval=$1   # 绝对值是本身.elselet "absval = (( 0 - $1))"   # 否则,改变它的符号.fireturn $absval
}tolower()   # 把传递的字符串转为小写
{if [ -z "$1" ];then   # 如果没有传递参数,打印错误信息,然后从函数中返回.echo "(null)"returnfiecho "$@" | tr A-Z a-zreturn
}

在脚本中添加特殊种类的注释开头标识有助于条理清晰和可读性。

##表示注意
rm -rf *.zzy  ##"rm"命令的"-rf"组合选项非常的危险,尤其是对通配符而言.#+ 表示继续上一行.
# 这是第一行
#+ 这是多行的注释,
#+ 这里是最后一行.#* 表示标注
#o表示列表项
#>表示另一个观点while [ "$var1" != "end" ]   #> while test "$var1" != "end"

if-test 结构的一种聪明用法是用来注释一块代码块。

#!/bin/bash
#
COMMENT_BLOCK=
# 给上面的变量设置某个值就会产生讨厌的结果if [ $COMMENT_BLOCK ];then
Comment block --
=================================
This is a comment line.
This is another comment line.
This is yet another comment line.
=================================echo "This will not echo."Comment blocks are error-free! Whee!
fiecho "No more comments, please."
exit 0

测试$? 退出状态变量,因为一个脚本可能想要测试一个参数是否只包含数字,以便后面 可以把它当作一个整数。

#!/bin/bash
#
SUCCESS=0
E_BADINPUT=65test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null
# 整数要么等于零要么不等于零.
# 2>/dev/null 可以抑制错误信息.if [ $? -ne "$SUCCESS" ];thenecho "Usage: `basename $0` integer-input"exit $E_BADINPUT
filet "sum = $1 + 25"   # 如果$1 不是整数就会产生错误.
echo "Sum = $sum"
exit 0

0-255
范围的函数返回值是个严格的限制。用全局变量和其他方法常常出问题。函数内返回值给脚本主体的另一个办法是让函数写值到标准输出(通常是用
echo) 作为"返回值",并且将其赋给一个变量。这实际是命令替换(command substitution)的变体。

#!/bin/bash
#
multiply()   # 传递乘数.能接受多个参数.
{local product=1until [ -z "$1" ]  # 直到所有参数都处理完毕...dolet "product *= $1"shiftdoneecho $product   # 不会打印到标准输出,因为要把它赋给一个变量.
}
mult1=15383; mult2=25211
val1=`multiply $mult1 $mult2`
echo "$mult1 X $mult2 = $val1"mult1=25; mult2=5; mult3=20
val2=`multiply $mult1 $mult2 $mult3`
echo "$mult1 X $mult2 X $mult3 = $val2"mult1=188; mult2=37; mult3=25; mult4=47
val3=`multiply $mult1 $mult2 $mult3 $mult4`
echo "$mult1 X $mult2 X $mult3 X $mult4 = $val3"
exit 0

相同的技术也可用在字符串中。这意味着函数可以"返回"一个非数字的值

capitalize_ichar()   # 把传递来的参数字符串的第一个字母大写
{string0="$@"   # 能接受多个参数.firstchar=${string0:0:1}   # 第一个字符.string1=${string0:1}   # 余下的字符.FirstChar=`echo "$firstchar" | tr a-z A-Z`   # 第一个字符转换成大写字符.echo "$FirstChar$string1"
}
newstring=`capitalize_ichar`
echo "$newstring"

用这个办法甚至可能"返回"多个值

Example 33-16 整型还是string?
#!/bin/bash
#
sum_and_product()   # 计算所传参数的总和与乘积.
{echo $(($1 + $2)) $(($1 * $2))   # 打印每个计算的值,用空格分隔开.
}
echo
echo "输入第一个数字"
read first
echo
echo "输入第二个数字"
read second
echoretval=`sum_and_product $first $second`   # 把函数的输出赋值给变量.
sum=`echo "$retval" | awk '{print $1}'`   # 把第一个域的值赋给 sum 变量.
product=`echo "$retval" | awk '{print $2}'`   # 把第二个域的值赋给 product 变量.echo "$first + $second = $sum"
echo "$first * $second = $product"
echo
exit 0

传递数组给函数,然后"返回"一个数组给脚本

用变量替换(command substitution)把数组的所有元素用空格分隔开来并赋给一个变量 就可以实现给函数传递数组。用先前介绍的方法函数内 echo 一个数组并"返回此值",然后调用命令替换用 ( … ) 操作符赋值给一个数组。

Example 33-17 传递和返回数组

#!/bin/bash
#
Pass_Array()
{local passed_array   # 局部变量.passed_array=(`echo "$1"`)echo "${passed_array[@]}"
}
original_array=( element1 element2 element3 element4 element5 )
echo
echo "original_array = ${original_array[@]}"#下面是传递数组给函数的技巧
argument=`echo ${original_array[@]}`
#把原数组的所有元素用空格分隔开合成一个字符串并赋给一个变量
#注意:只是把数组本身传给函数是不会工作的.#下面是允许数组作为"返回值"的技巧.
returned_array=(`Pass_Array "$argument"`)
#把函数的输出赋给数组变量.echo "returned_array = ${returned_array[@]}"#尝试在函数外存取(列出)数组.
Pass_Array "$argument"#函数本身可以列出数组,但...函数外存取数组被禁止.
echo "Passed array (within function) = ${passed_array[@]}"
## 因为变量是函数内的局部变量,所以只有 NULL 值.
echo
exit 0

利用双括号结构,使在for和while循环中可以使用C风格的语法来设置和增加变量。参考例子10-12和例子10-17

在脚本开头设置path和umask增加脚本的"可移植性"–在某些把$PATH和umask弄乱的系统里也可以运行

#!/bin/bash
#
PATH=/bin:/usr/bin:/usr/local/bin ; export PATH
umask 022   # 脚本的创建的文件有 755 的权限设置.

一个有用的脚本技术是:重复地把一个过滤器的输出回馈(用管道)给另一个相同过滤器,但过滤器有不同的参数和选项。尤其对tr和grep更合适。

wlist=`string "$1" | tr A-Z a-z | tr '[:space:] Z' | tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

Example 33-18 anagrams游戏

#!/bin/bash
#
LETTERSET="eta...oinanagrams...shrdanagrams...luanagrams"
FILTER='.......'
echo "$LETTERSET" |
grep "$FILTER" |   # 至少 7 个字符,
grep '^is' |   # 以'is'开头
grep -v 's$' |   # 不是复数的(指英文单词复数)
grep -v 'ed$'   # 不是过去式的(当然也是英文单词)
exit 0

使用"匿名的 here documents" 来注释代码块,这样避免了对代码块的每一块单独用#来注释了。参考例子 17-11

当依赖某个命令脚本在一台没有安装该命令的机器上运行时会出错。使用 whatis 命令可以避免此问题。

CMD=command1
PlanB=command2command_test=$(whatis "$CMD" | grep 'nothing appropriate')
# 如果'command1'没有在系统里发现 , 'whatis'会返回:"command1: nothing appropriate."if [[ -z "$command_test" ]];then   # 检查命令是否存在.$CMD option1 option2
else$PlanB
fi

在发生错误的情况下if-grep test 可能不会返回期望的结果,因为文本是打印在标准出错而不是标准输出上。

if ls -l nonexistent_filename | grep -q 'No such file or directory'
thenecho "File \"nonexistent_filename\" does not exist."
fi

把标准出错重定向到标准输出上可以修改这个

if ls -l nonexistent_filename 2>&1 | grep -q 'No such file or directory'
thenecho "File \"nonexistent_filename\" does not exist."
fi

在 shell 脚本里能调用 X-Windows 的窗口小部件将多么美好。已经存在有几种工具包实现这个了,它们称为
Xscript、Xmenu 和 widtools。头两个已经不再维护。

注意: widtools (widget tools) 工具包要求安装了 XForms 库。另外,它的 Makefile 在典型的 Linux 系统上安装前需要做一些合适的编辑。最后,提供的 6 个部件有 3 个 不能工作 (事实上会发生段错误)。

dialog 工具集提供了 shell 脚本使用一种称为"对话框"的窗口部件。原始的 dialog 软件包工作在文本模式的控制台下,但它的后续软件 gdialog、Xdialog 和 kdialog 使用基于 X-Windows 的窗口小部件集。

Example 33-19 在shell脚本中调用的窗口部件

#!/bin/bash
#
#必须在你的系统里安装'dialog'才能运行此脚本.# 在窗口中的输入错误.
E_INPUT=65
# 输入窗口显示的尺寸.
HEIGHT=50
WIDTH=60# 输出文件名 (由脚本名构建而来).
OUTFILE=$0.output# 把这个脚本的内容显示在窗口中.
dialog --title "Displaying: $0" --textbox $0 $HEIGHT $WIDTH# 现在,保存输入到输出文件中.
echo -n "VARIABLE=" > $OUTFILE
dialog --title "User Input" --inputbox "Enter variable, please:" $HEIGHT $WIDTH 2>> $OUTFILEif [ "$?" -eq 0 ];thenecho "Executed \"dialog box\" without errors."
elseecho "Error(s) in \"dialog box\" execution."rm $OUTFILEexit $E_INPUT
fi#  现在,我们重新取得并显示保存的变量.
. $OUTFILE # 'Source' 保存的文件(即执行).
echo "The variable input in the \"input box\" was: "$VARIABLE""rm $OUTFILE # 清除临时文件.
exit $?

其他的在脚本中使用窗口的工具还有 Tk 或 wish (Tcl 派生物),PerlTk (Perl 的 Tk 扩展),tksh (ksh 的 Tk 扩展),XForms4Perl (Perl 的 XForms 扩展),Gtk-Perl (Perl 的 Gtk 扩展)或 PyQt (Python 的 Qt 扩展)。

为了对复杂的脚本做多次的版本修订管理,可以使用 rcs 软件包。

使用这个软件包的好处之一是会自动地升级 ID 头标识。在 rcs 的 co 命令处理一些预定义的关键字参数替换。例如:代替脚本里头#$Id$的,如类似下面的行:

#$Id: hello-world.sh,v 1.1 2004/10/16 02:43:05 bozo Exp $

33.8 安全话题

33.8.1 被感染的脚本

有一个简短的关于脚本安全的介绍是适当的。脚本程序可能会包含蠕虫病毒、特洛伊木马或是其他的病毒。由于这些原因,决不要以 root 身份运行脚本 (或允许它被插入到系统的 /etc/rc.d 里的启动脚本中) 除非你确定这是值得信赖的源码或你已经很小心地分析过了脚本并确信它不会有什么危害。

33.8.2 隐藏Shell脚本源码

为了安全,使脚本不可读是有必要的。如果有软件可以把脚本转化成相应的二进制执行文件就好了。Francisco Rosales 的 shc - 通用的 Shell 脚本编译器(generic shell script compiler) 可以出色地完成目标。

33.9 移植话题

以现在的情况来看,许多种 shell 和脚本语言都尽力使自己符合 POSIX 1003.2 标准。用 --posix 选项调用 Bash 或在脚本开头插入 set -o posix 就能使 Bash 能以很接近这个标准的方式运行。在脚本开头用"#!/bin/sh" 比用 "#!/bin/bash"会更好。
注意:在 Linux 和一些 UNIX 风格的系统里/bin/sh 是/bin/bash 的一个链接(link),并且如果脚本以/bin/sh 调用时会禁用 Bash 的扩展功能。

33.10 在Windows下进行shell编程

使用其他操作系统用户希望能运行 UNIX 类型的脚本能在他们的系统上运行,因此也希望能在这本书里能学到这方面的知识。来自 Cygnus 的 Cygwin 软件结合来自 Mortice Kern 的 MKS 软件包(MKS utilities)可以给 Windows 添加 shell 脚本的兼容。

高级shell编程笔记(第三十三章 杂项)相关推荐

  1. 高级shell编程笔记(第十二章 外部过滤器,程序和命令)

    第十二章 外部过滤器,程序和命令 标准的UNIX命令使得脚本更加灵活.通过简单的编程结构把shell指令和系统命令结合起来,这才是脚本能力的所在. 12.1 基本命令 新手必须掌握的初级命令 ls 基 ...

  2. 高级shell编程笔记(第十三章 系统与管理命令)

    第十三章 系统与管理命令 在/etc/rc.d 目录中的启动和关机脚本中包含了好多有用的(和没用的)这些系统管理命令.这些命令通常总是被 root 用户使用,用与系统维护或者是紧急文件系统修复.一定要 ...

  3. JS高级程序设计读书笔记(第十三章 事件)

    第十三章 事件 JavaScript 与 HTML 之间的交互是通过事件实现的.事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间.可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代 ...

  4. 程序员编程艺术第三十二~三十三章:最小操作数,木块砌墙问题

    第三十二~三十三章:最小操作数,木块砌墙问题 作者:July.caopengcs.红色标记.致谢:fuwutu.demo. 时间:二零一三年八月十二日 题记 再过一两月,便又到了每年的九月十月校招高峰 ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 原文: Int ...

  6. 李弘毅机器学习笔记:第十三章—CNN

    李弘毅机器学习笔记:第十三章-CNN 为什么用CNN Small region Same Patterns Subsampling CNN架构 Convolution Propetry1 Propet ...

  7. 【正点原子Linux连载】第三十三章 U-Boot移植 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. 课工场-JAVA高级特性编程及实战第1章练习题3答案参考

    JAVA高级特性编程及实战第1章练习题3答案参考~ 本人菜鸟,一章章地学, 本想在网上搜一下然后对下答案的, 没找着~ 本着虔诚的心,把自己做的贴出来~ 运行结果是了出来了,过程不知道是否正确 欢迎大 ...

  9. 《快速掌握PyQt5》第三十三章 音频与视频

    第三十三章 音频与视频 33.1 QSound 33.2 QSoundEffect 33.3 QMovie 33.4 QMediaPlayer 33.5 制作简单音乐播放器 33.6 小结 <快 ...

最新文章

  1. python下什么版本-python版本有什么不同
  2. Python调用大漠插件
  3. 【模板】 全排列 有重复元素的全排列
  4. Cloud for Customer的前台请求是怎么发送到后台的
  5. 中国大数据与智能计算产业联盟“开源软件工作委员会”成立会议在京召开
  6. android 常用输入法,[转载]Android 系统输入法的调用
  7. 在Rammap(内存分析工具)的基础上实现自动优化
  8. 淘宝网登录滑动验证报错解决办法--改参数 让淘宝检测失败 2021-4
  9. 精辟!一文看懂layout与PCB的关系
  10. 政府支撑智慧城市建设 楼宇对讲投身社区成长
  11. solidworks动画制作教程——简单直线运动
  12. 伟创ac80b变频器故障代码_安川变频器故障代码和安川变频器报警详解
  13. Expandable实现方法
  14. python怎么自动化录入数据_报表自动化怎么做?Excel,Python,还是它?
  15. 一条对“失控的腾讯帝国:企鹅无法把控手机市场”的评论
  16. C++函数参数中的省略号
  17. 互联网运营面试题_产品运营面试常见问题
  18. 从前装量产数据看“软硬分离”与“市场博弈”
  19. 这些富人思维,学会任何一种都可以纵横互联网
  20. win10系统无法加载操作系统且自动恢复失败的解决方案

热门文章

  1. pyspider创建淘女郎图片爬虫任务-运行流程解析
  2. 专色油墨配色同色异谱怎么办?
  3. NCMMSC 2021丨希尔贝壳参加第十六届全国人机语音通讯学术会议
  4. REPAIR TABLE导致死锁
  5. 约书亚·布洛赫(Joshua Bloch):Bumper-Sticker API设计
  6. 电脑华硕A455L系列,机械硬盘换成固态硬盘,光驱位改放机械硬盘
  7. 7.1 Java(农夫果园【2】:一个农场,专门种植销售各类水果,在这个系统中需要描述下列水果葡萄、草莓、苹果)
  8. ARM的 N、Z、C、V 标志位的解释
  9. 知识回顾:什么是封装?封装的作用?如何封装?
  10. 搜狐新闻如何玩?张朝阳演讲透露玄机