33.5. 将脚本"彩色化"

ANSI[1]定义了屏幕属性的转义序列集合, 比如说粗体文本, 前景与背景颜色. DOS批处理文件通常都使用ANSI转义码来控制 颜色输出, Bash脚本也是这么做的.


例子 33-11. 一个"彩色的"地址数据库

#!/bin/bash
# ex30a.sh: 脚本ex30.sh的"彩色"版本. 
#            没被加工处理过的地址数据库


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 "(Enter only the first letter of name.)"
echo
echo -en '\E[47;34m'"\033[1mE\033[0m"   # 蓝色
tput sgr0                               # 将颜色重置为"常规". 
echo "vans, Roland"                     # "[E]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"
echo

read person

case "$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"
  ;;

# 稍后为Smith & Zane添加信息. 

          * )
   # 默认选项. 	  
   # 空输入(直接按回车)也会在这被匹配. 
   echo
   echo "Not yet in database."
  ;;

esac

tput sgr0                               # 将颜色重置为"常规". 

echo

exit 0


例子 33-12. 画一个盒子

#!/bin/bash
# Draw-box.sh: 使用ASCII字符画一个盒子. 

# 由Stefano Palmeri编写, 本书作者做了少量修改. 
# 经过授权, 可以在本书中使用. 


######################################################################
###  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, 
#+ 不会有消息打印到stderr上. 
#
#  开始画盒子之前, 会清屏. 
#  函数内不包含清屏命令. 
#  这样就允许用户画多个盒子, 甚至可以叠加多个盒子. 

###  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; then
   exit $E_BADARGS
fi

BOX_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 ]; then
   exit $E_BADARGS
fi
if [ `expr $1 + $BOX_HEIGHT + 1` -gt $T_ROWS ]; then
   exit $E_BADARGS
fi
if [ `expr $2 + $BOX_WIDTH + 1` -gt $T_COLS ]; then
   exit $E_BADARGS
fi
if [ $3 -lt 1 ] || [ $4 -lt 1 ]; then
   exit $E_BADARGS
fi                                 # 参数检查结束. 

plot_char(){                       # 函数内的函数. 
   echo -e "\E[${1};${2}H"$3
}

echo -ne "\E[3${5}m"               # 如果定义了, 就设置盒子边框的颜色. 

# 开始画盒子

count=1                                         #  使用plot_char函数
for (( r=$1; count<=$BOX_HEIGHT; r++)); do      #+ 画垂直线. 
  plot_char $r $2 $VERT
  let count=count+1
done 

count=1
c=`expr $2 + $BOX_WIDTH`
for (( r=$1; count<=$BOX_HEIGHT; r++)); do
  plot_char $r $c $VERT
  let count=count+1
done 

count=1                                        #  使用plot_char函数
for (( c=$2; count<=$BOX_WIDTH; c++)); do      #+ 画水平线. 
  plot_char $1 $c $HORZ
  let count=count+1
done 

count=1
r=`expr $1 + $BOX_HEIGHT`
for (( c=$2; count<=$BOX_WIDTH; c++)); do
  plot_char $r $c $HORZ
  let count=count+1
done 

plot_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"表示转义序列结束.
bash$ echo -e "\033[1mThis is bold text.\033[0m"
	      

一种类似的转义序列用来切换下划线属性(在rxvtaterm上).
bash$ echo -e "\033[4mThis is underlined text.\033[0m"
	      

Note

echo命令的-e选项用来启用转义序列.

其他的转义序列可用于修改文本和背景色.

bash$ echo -e '\E[34;47mThis prints in blue.'; tput sgr0


bash$ echo -e '\E[33;44m'"yellow text on blue background"; tput sgr0


bash$ echo -e '\E[1;33;44m'"BOLD yellow text on blue background"; tput sgr0
	      

Note

通常情况下, 为浅色的前景文本设置粗体属性比较好.

tput sgr0把终端设置恢复为原样. 如果省略这一句, 那么这个终端所有后续的输出还会是蓝色.

Note

因为tput sgr0在某些环境下不能恢复终端设置, echo -ne \E[0m可能是更好的选择.

下表的数值是在rxvt终端上运行的结果. 具体的结果可能和在其他终端上运行的结果不同.


表格 33-1. 转义序列中颜色与数值的对应

颜色前景背景
3040
3141
绿3242
3343
3444
洋红3545
3646
3747


例子 33-13. 显示彩色文本

#!/bin/bash
# color-echo.sh: 使用颜色来显示文本消息. 

# 可以按照你自己的目的来修改这个脚本. 
# 这比将颜色数值写死更容易. 

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 ()                     # Color-echo.
                             # 参数$1 = 要显示的信息
                             # 参数$2 = 颜色
{
local default_msg="No message passed."
                             # 其实并不一定非的是局部变量. 

message=${1:-$default_msg}   # 默认为default_msg. 
color=${2:-$black}           # 如果没有指定, 默认为黑色. 

  echo -e "$color"
  echo "$message"
  Reset                      # 重置文本属性. 

  return
}  


# 现在, 让我们试一下. 
# ----------------------------------------------------
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参数. 
# ----------------------------------------------------

echo

exit 0

# 练习:
# -----
# 1) 为'cecho ()'函数添加"粗体"属性. 
# 2) 为彩色背景添加选项. 


例子 33-14. "赛马"游戏

#!/bin/bash
# horserace.sh: 一个非常简单的模拟赛马的游戏. 
# 作者: Stefano Palmeri
# 经过授权可以在本书中使用. 

################################################################
#  脚本目的: 
#  使用转义序列和终端颜色进行游戏. 
#
#  练习: 
#  编辑脚本, 使它运行起来更具随机性, 
#+ 建立一个假的赌场 . . .     
#  嗯 . . . 嗯 . . . 这种开场让我联想起一部电影 . . .
#
#  脚本将会给每匹马分配一个随机障碍. 
#  按照马的障碍来计算几率, 
#+ 并且使用一种欧洲(?)的风格表达出来. 
#  比如: 几率(odds)=3.75的话, 那就意味着如果你押$1,
#+ 你就会赢得$3.75.
# 
#  此脚本已经在GNU/Linux操作系统上测试过, 
#+ 测试终端有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  

#  如果脚本被Ctl-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 

# 画6条白线. 
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}
      esac


done


# 打印几率. 
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_odds

echo -ne '\E[47m'
# 等待按下回车键, 按下之后就开始比赛. 
# 转义序列'\E[?25l'禁用光标. 
tput cup 17 0
echo -e '\E[?25l'Press [enter] key to start the race...
read -s

#  禁用了终端的常规echo功能. 
#  这么做用来避免在比赛中, 
#+ 按键所导致的"花"屏. 
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`

# 下面的"while"结构需要使用COL变量. 
COL=0    

while [ $COL -lt $WINNING_POS ]; do
                   
          MOVE_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_DIR

tput cup 19 0

exit 0

也请参考例子 A-22.

Caution

然而, 这里有一个严重的问题. ANSI转义序列是不可移植的. 在某些终端(或控制台)上运行的好好的代码, 可能在其他终端上根本没办法运行. "彩色"的脚本可能会在脚本作者的机器上运行的非常好, 但是在其他人的机器上就可能产生不可读的输出. 因为这个原因, 使得"彩色"脚本的用途大打折扣, 而且很有可能使得这项技术变成华而不实的小花招, 甚至成为一个"玩具".

Moshe Jacobson的彩色工具(http://runslinux.net/projects.html#color)能够非常容易的简化ANSI转义序列的使用. 这个工具使用清晰而且富有逻辑的语法代替了之前讨论的难用的结构.

Henry/teikedvl也开发了一个类似的工具(http://scriptechocolor.sourceforge.net/)用来简化彩色脚本的创建.

注意事项

[1]

当然, ANSI是美国国家标准组织(American National Standards Institute)的缩写. 这个令人敬畏的组织建立和维护着许多技术和工业的标准.