内建命令 (builtin) 是 Bash 工具集中包含的 命令,字面意思是 内置的 (built in)。这要么是出于性能原因 -- 内建命令的执行速度比外部命令更快,外部命令通常需要 派生 (forking off) [1] 一个单独的进程 -- 要么是因为特定的内建命令需要直接访问 shell 内部。
当一个命令或 shell 本身启动(或 衍生 (spawns))一个新的子进程来执行任务时,这被称为 派生 (forking)。这个新进程是子进程 (child),而 派生 (forked) 它出来的进程是 父进程 (parent)。当 子进程 (child process) 正在工作时,父进程 (parent process) 仍在执行。 请注意,虽然 父进程 (parent process) 获取 子进程 (child process) 的 进程 ID (process ID),因此可以将参数传递给它,但反过来则不行。这可能会产生微妙且难以追踪的问题。 例 15-1. 一个派生自身多个实例的脚本
通常,Bash 内建命令 (builtin) 在脚本中执行时不会派生子进程。脚本中的外部系统命令或过滤器通常会派生子进程。 |
内建命令可能是同名系统命令的同义词,但 Bash 在内部重新实现了它。例如,Bash 的 echo 命令与以下命令不同:/bin/echo,尽管它们的行为几乎相同。
#!/bin/bash echo "This line uses the \"echo\" builtin." /bin/echo "This line uses the /bin/echo system command." |
关键字 (keyword) 是 保留 (reserved) 字、令牌或运算符。关键字对 shell 具有特殊含义,实际上是 shell 语法的构建块。例如,for、while、do 和 ! 都是关键字。与 内建命令 (builtin) 类似,关键字被硬编码到 Bash 中,但与 内建命令 (builtin) 不同,关键字本身不是命令,而是命令结构的子单元。[2]
打印(到stdout)表达式或变量(参见 例 4-1)。
echo Hello echo $a |
echo 需要-e选项来打印转义字符。参见 例 5-2。
通常,每个 echo 命令都会打印一个终端换行符,但-n选项会抑制此行为。
![]() | echo 可以用于将一系列命令通过管道传递下去。
|
请注意,echo `command` 会删除command生成的任何换行符。
$IFS (内部字段分隔符) 变量通常包含 \n (换行符) 作为其 空白字符 集之一。因此,Bash 将command的输出在换行符处拆分为 echo 的参数。然后 echo 输出这些参数,以空格分隔。
bash$ ls -l /usr/share/apps/kjezz/sounds -rw-r--r-- 1 root root 1407 Nov 7 2000 reflect.au -rw-r--r-- 1 root root 362 Nov 7 2000 seconds.au bash$ echo `ls -l /usr/share/apps/kjezz/sounds` total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root ... |
那么,我们如何将换行符嵌入到 echo 的字符串中呢?
# Embedding a linefeed? echo "Why doesn't this string \n split on two lines?" # Doesn't split. # Let's try something else. echo echo $"A line of text containing a linefeed." # Prints as two distinct lines (embedded linefeed). # But, is the "$" variable prefix really necessary? echo echo "This string splits on two lines." # No, the "$" is not needed. echo echo "---------------" echo echo -n $"Another line of text containing a linefeed." # Prints as two distinct lines (embedded linefeed). # Even the -n option fails to suppress the linefeed here. echo echo echo "---------------" echo echo # However, the following doesn't work as expected. # Why not? Hint: Assignment to a variable. string1=$"Yet another line of text containing a linefeed (maybe)." echo $string1 # Yet another line of text containing a linefeed (maybe). # ^ # Linefeed becomes a space. # Thanks, Steve Parker, for pointing this out. |
![]() | 此命令是 shell 内建命令,与/bin/echo不同,尽管其行为类似。
|
printf,格式化打印命令,是增强型的 echo。它是 C 语言printf()库函数的有限变体,其语法略有不同。
printf 格式字符串... 参数...
这是 Bash 内建命令 (builtin) 版本的/bin/printf或/usr/bin/printf命令。有关深入介绍,请参阅 printf 手册页(系统命令)。
![]() | 旧版本的 Bash 可能不支持 printf。 |
例 15-2. printf 的实际应用
#!/bin/bash # printf demo declare -r PI=3.14159265358979 # Read-only variable, i.e., a constant. declare -r DecimalConstant=31373 Message1="Greetings," Message2="Earthling." echo printf "Pi to 2 decimal places = %1.2f" $PI echo printf "Pi to 9 decimal places = %1.9f" $PI # It even rounds off correctly. printf "\n" # Prints a line feed, # Equivalent to 'echo' . . . printf "Constant = \t%d\n" $DecimalConstant # Inserts tab (\t). printf "%s %s \n" $Message1 $Message2 echo # ==========================================# # Simulation of C function, sprintf(). # Loading a variable with a formatted string. echo Pi12=$(printf "%1.12f" $PI) echo "Pi to 12 decimal places = $Pi12" # Roundoff error! Msg=`printf "%s %s \n" $Message1 $Message2` echo $Msg; echo $Msg # As it happens, the 'sprintf' function can now be accessed #+ as a loadable module to Bash, #+ but this is not portable. exit 0 |
格式化错误消息是 printf 的一个有用应用
E_BADDIR=85 var=nonexistent_directory error() { printf "$@" >&2 # Formats positional params passed, and sends them to stderr. echo exit $E_BADDIR } cd $var || error $"Can't cd to %s." "$var" # Thanks, S.C. |
另请参阅 例 36-17。
“读取”来自stdin的变量值,即,从键盘交互式获取输入。-a选项允许 read 获取数组变量(参见 例 27-6)。
例 15-3. 使用 read 进行变量赋值
#!/bin/bash # "Reading" variables. echo -n "Enter the value of variable 'var1': " # The -n option to echo suppresses newline. read var1 # Note no '$' in front of var1, since it is being set. echo "var1 = $var1" echo # A single 'read' statement can set multiple variables. echo -n "Enter the values of variables 'var2' and 'var3' " echo =n "(separated by a space or tab): " read var2 var3 echo "var2 = $var2 var3 = $var3" # If you input only one value, #+ the other variable(s) will remain unset (null). exit 0 |
没有关联变量的 read 将其输入分配给专用变量 $REPLY。
例 15-4. 当 read 没有变量时会发生什么
#!/bin/bash # read-novar.sh echo # -------------------------- # echo -n "Enter a value: " read var echo "\"var\" = "$var"" # Everything as expected here. # -------------------------- # echo # ------------------------------------------------------------------- # echo -n "Enter another value: " read # No variable supplied for 'read', therefore... #+ Input to 'read' assigned to default variable, $REPLY. var="$REPLY" echo "\"var\" = "$var"" # This is equivalent to the first code block. # ------------------------------------------------------------------- # echo echo "=========================" echo # This example is similar to the "reply.sh" script. # However, this one shows that $REPLY is available #+ even after a 'read' to a variable in the conventional way. # ================================================================= # # In some instances, you might wish to discard the first value read. # In such cases, simply ignore the $REPLY variable. { # Code block. read # Line 1, to be discarded. read line2 # Line 2, saved in variable. } <$0 echo "Line 2 of this script is:" echo "$line2" # # read-novar.sh echo # #!/bin/bash line discarded. # See also the soundcard-on.sh script. exit 0 |
通常,输入\在输入到 read 时会抑制换行符。-r选项使输入的\被字面解释。
例 15-5. read 的多行输入
#!/bin/bash echo echo "Enter a string terminated by a \\, then press <ENTER>." echo "Then, enter a second string (no \\ this time), and again press <ENTER>." read var1 # The "\" suppresses the newline, when reading $var1. # first line \ # second line echo "var1 = $var1" # var1 = first line second line # For each line terminated by a "\" #+ you get a prompt on the next line to continue feeding characters into var1. echo; echo echo "Enter another string terminated by a \\ , then press <ENTER>." read -r var2 # The -r option causes the "\" to be read literally. # first line \ echo "var2 = $var2" # var2 = first line \ # Data entry terminates with the first <ENTER>. echo exit 0 |
read 命令有一些有趣的选项,允许回显提示符,甚至无需按 ENTER 键即可读取击键。
# Read a keypress without hitting ENTER. read -s -n1 -p "Hit a key " keypress echo; echo "Keypress was "\"$keypress\""." # -s option means do not echo input. # -n N option means accept only N characters of input. # -p option means echo the following prompt before reading input. # Using these options is tricky, since they need to be in the correct order. |
的-n选项到 read 也允许检测 方向键 和某些其他不常用的键。
例 15-6. 检测方向键
#!/bin/bash # arrow-detect.sh: Detects the arrow keys, and a few more. # Thank you, Sandro Magi, for showing me how. # -------------------------------------------- # Character codes generated by the keypresses. arrowup='\[A' arrowdown='\[B' arrowrt='\[C' arrowleft='\[D' insert='\[2' delete='\[3' # -------------------------------------------- SUCCESS=0 OTHER=65 echo -n "Press a key... " # May need to also press ENTER if a key not listed above pressed. read -n3 key # Read 3 characters. echo -n "$key" | grep "$arrowup" #Check if character code detected. if [ "$?" -eq $SUCCESS ] then echo "Up-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$arrowdown" if [ "$?" -eq $SUCCESS ] then echo "Down-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$arrowrt" if [ "$?" -eq $SUCCESS ] then echo "Right-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$arrowleft" if [ "$?" -eq $SUCCESS ] then echo "Left-arrow key pressed." exit $SUCCESS fi echo -n "$key" | grep "$insert" if [ "$?" -eq $SUCCESS ] then echo "\"Insert\" key pressed." exit $SUCCESS fi echo -n "$key" | grep "$delete" if [ "$?" -eq $SUCCESS ] then echo "\"Delete\" key pressed." exit $SUCCESS fi echo " Some other key pressed." exit $OTHER # ========================================= # # Mark Alexander came up with a simplified #+ version of the above script (Thank you!). # It eliminates the need for grep. #!/bin/bash uparrow=$'\x1b[A' downarrow=$'\x1b[B' leftarrow=$'\x1b[D' rightarrow=$'\x1b[C' read -s -n3 -p "Hit an arrow key: " x case "$x" in $uparrow) echo "You pressed up-arrow" ;; $downarrow) echo "You pressed down-arrow" ;; $leftarrow) echo "You pressed left-arrow" ;; $rightarrow) echo "You pressed right-arrow" ;; esac exit $? # ========================================= # # Antonio Macchi has a simpler alternative. #!/bin/bash while true do read -sn1 a test "$a" == `echo -en "\e"` || continue read -sn1 a test "$a" == "[" || continue read -sn1 a case "$a" in A) echo "up";; B) echo "down";; C) echo "right";; D) echo "left";; esac done # ========================================= # # Exercise: # -------- # 1) Add detection of the "Home," "End," "PgUp," and "PgDn" keys. |
![]() | 的-n选项到 read 不会检测 ENTER (换行) 键。 |
的-t选项到 read 允许定时输入(参见 例 9-4 和 例 A-41)。
-u选项接受目标文件的 文件描述符。
read 命令也可以从 重定向 到stdin的文件中 “读取” 其变量值。如果文件包含多行,则只有第一行被分配给变量。如果 read 有多个参数,则这些变量中的每一个都会被分配一个连续的 空格分隔 字符串。注意!
例 15-7. 将 read 与 文件重定向 一起使用
#!/bin/bash read var1 <data-file echo "var1 = $var1" # var1 set to the entire first line of the input file "data-file" read var2 var3 <data-file echo "var2 = $var2 var3 = $var3" # Note non-intuitive behavior of "read" here. # 1) Rewinds back to the beginning of input file. # 2) Each variable is now set to a corresponding string, # separated by whitespace, rather than to an entire line of text. # 3) The final variable gets the remainder of the line. # 4) If there are more variables to be set than whitespace-terminated strings # on the first line of the file, then the excess variables remain empty. echo "------------------------------------------------" # How to resolve the above problem with a loop: while read line do echo "$line" done <data-file # Thanks, Heiner Steven for pointing this out. echo "------------------------------------------------" # Use $IFS (Internal Field Separator variable) to split a line of input to # "read", if you do not want the default to be whitespace. echo "List of all users:" OIFS=$IFS; IFS=: # /etc/passwd uses ":" for field separator. while read name passwd uid gid fullname ignore do echo "$name ($fullname)" done </etc/passwd # I/O redirection. IFS=$OIFS # Restore original $IFS. # This code snippet also by Heiner Steven. # Setting the $IFS variable within the loop itself #+ eliminates the need for storing the original $IFS #+ in a temporary variable. # Thanks, Dim Segebart, for pointing this out. echo "------------------------------------------------" echo "List of all users:" while IFS=: read name passwd uid gid fullname ignore do echo "$name ($fullname)" done </etc/passwd # I/O redirection. echo echo "\$IFS still $IFS" exit 0 |
![]() | 使用 echo 将输出 管道 传输到 read 以设置变量 将会失败。 然而,管道传输 cat 的输出似乎有效。
然而,正如 Bj�n Eriksson 所展示的 例 15-8. 从管道读取时出现问题
gendiff 脚本,通常在/usr/bin在许多 Linux 发行版上,将 find 的输出管道传输到 while read 结构。
|
![]() | 可以将文本 粘贴 (paste) 到 read 的输入字段中(但不能是多行!)。参见 例 A-38。 |
常用的 cd 更改目录命令在脚本中找到了用武之地,在脚本中,命令的执行需要位于指定的目录中。
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -) |
的-P(物理) 选项到 cd 使其忽略符号链接。
cd - 更改为 $OLDPWD,即上一个工作目录。
![]() | 当给出两个正斜杠时,cd 命令的功能不符合预期。
|
此命令集是用于为工作目录添加书签的机制,是一种有序地在目录之间来回移动的方法。下推 堆栈 用于跟踪目录名称。选项允许对目录堆栈进行各种操作。
pushd 目录名将路径目录名推送到目录堆栈上(到堆栈的顶部),并同时将当前工作目录更改为目录名
popd 从目录堆栈中删除(弹出)顶部目录路径名,并同时将当前工作目录更改为现在位于堆栈 顶部 的目录。
dirs 列出目录堆栈的内容(将其与 $DIRSTACK 变量进行比较)。成功的 pushd 或 popd 将自动调用 dirs。
需要在不硬编码目录名称更改的情况下对当前工作目录进行各种更改的脚本可以很好地利用这些命令。请注意隐式的$DIRSTACK数组变量,可以从脚本内部访问,它保存了目录堆栈的内容。
例 15-9. 更改当前工作目录
#!/bin/bash dir1=/usr/local dir2=/var/spool pushd $dir1 # Will do an automatic 'dirs' (list directory stack to stdout). echo "Now in directory `pwd`." # Uses back-quoted 'pwd'. # Now, do some stuff in directory 'dir1'. pushd $dir2 echo "Now in directory `pwd`." # Now, do some stuff in directory 'dir2'. echo "The top entry in the DIRSTACK array is $DIRSTACK." popd echo "Now back in directory `pwd`." # Now, do some more stuff in directory 'dir1'. popd echo "Now back in original working directory `pwd`." exit 0 # What happens if you don't 'popd' -- then exit the script? # Which directory do you end up in? Why? |
let 命令对变量执行算术运算。[3] 在许多情况下,它的功能类似于 expr 的简化版本。
例 15-10. 让 let 执行算术运算。
#!/bin/bash echo let a=11 # Same as 'a=11' let a=a+5 # Equivalent to let "a = a + 5" # (Double quotes and spaces make it more readable.) echo "11 + 5 = $a" # 16 let "a <<= 3" # Equivalent to let "a = a << 3" echo "\"\$a\" (=16) left-shifted 3 places = $a" # 128 let "a /= 4" # Equivalent to let "a = a / 4" echo "128 / 4 = $a" # 32 let "a -= 5" # Equivalent to let "a = a - 5" echo "32 - 5 = $a" # 27 let "a *= 10" # Equivalent to let "a = a * 10" echo "27 * 10 = $a" # 270 let "a %= 8" # Equivalent to let "a = a % 8" echo "270 modulo 8 = $a (270 / 8 = 33, remainder $a)" # 6 # Does "let" permit C-style operators? # Yes, just as the (( ... )) double-parentheses construct does. let a++ # C-style (post) increment. echo "6++ = $a" # 6++ = 7 let a-- # C-style decrement. echo "7-- = $a" # 7-- = 6 # Of course, ++a, etc., also allowed . . . echo # Trinary operator. # Note that $a is 6, see above. let "t = a<7?7:11" # True echo $t # 7 let a++ let "t = a<7?7:11" # False echo $t # 11 exit |
![]() | let 命令在某些情况下会返回令人惊讶的 退出状态。
|
eval arg1 [arg2] ... [argN]
将表达式或表达式列表中的参数组合起来并求值它们。表达式中的任何变量都会被扩展。最终结果是将字符串转换为命令。
![]() | eval 命令可用于从命令行或脚本中生成代码。 |
bash$ command_string="ps ax" bash$ process="ps ax" bash$ eval "$command_string" | grep "$process" 26973 pts/3 R+ 0:00 grep --color ps ax 26974 pts/3 R+ 0:00 ps ax |
每次调用 eval 都会强制对其参数进行重新求值。
a='$b' b='$c' c=d echo $a # $b # First level. eval echo $a # $c # Second level. eval eval echo $a # d # Third level. # Thank you, E. Choroba. |
例 15-11. 展示 eval 的效果
#!/bin/bash # Exercising "eval" ... y=`eval ls -l` # Similar to y=`ls -l` echo $y #+ but linefeeds removed because "echoed" variable is unquoted. echo echo "$y" # Linefeeds preserved when variable is quoted. echo; echo y=`eval df` # Similar to y=`df` echo $y #+ but linefeeds removed. # When LF's not preserved, it may make it easier to parse output, #+ using utilities such as "awk". echo echo "===========================================================" echo eval "`seq 3 | sed -e 's/.*/echo var&=ABCDEFGHIJ/'`" # var1=ABCDEFGHIJ # var2=ABCDEFGHIJ # var3=ABCDEFGHIJ echo echo "===========================================================" echo # Now, showing how to do something useful with "eval" . . . # (Thank you, E. Choroba!) version=3.4 # Can we split the version into major and minor #+ part in one command? echo "version = $version" eval major=${version/./;minor=} # Replaces '.' in version by ';minor=' # The substitution yields '3; minor=4' #+ so eval does minor=4, major=3 echo Major: $major, minor: $minor # Major: 3, minor: 4 |
例 15-12. 使用 eval 在变量之间进行选择
#!/bin/bash # arr-choice.sh # Passing arguments to a function to select #+ one particular variable out of a group. arr0=( 10 11 12 13 14 15 ) arr1=( 20 21 22 23 24 25 ) arr2=( 30 31 32 33 34 35 ) # 0 1 2 3 4 5 Element number (zero-indexed) choose_array () { eval array_member=\${arr${array_number}[element_number]} # ^ ^^^^^^^^^^^^ # Using eval to construct the name of a variable, #+ in this particular case, an array name. echo "Element $element_number of array $array_number is $array_member" } # Function can be rewritten to take parameters. array_number=0 # First array. element_number=3 choose_array # 13 array_number=2 # Third array. element_number=4 choose_array # 34 array_number=3 # Null array (arr3 not allocated). element_number=4 choose_array # (null) # Thank you, Antonio Macchi, for pointing this out. |
例 15-13. 回显 命令行参数
#!/bin/bash # echo-params.sh # Call this script with a few command-line parameters. # For example: # sh echo-params.sh first second third fourth fifth params=$# # Number of command-line parameters. param=1 # Start at first command-line param. while [ "$param" -le "$params" ] do echo -n "Command-line parameter " echo -n \$$param # Gives only the *name* of variable. # ^^^ # $1, $2, $3, etc. # Why? # \$ escapes the first "$" #+ so it echoes literally, #+ and $param dereferences "$param" . . . #+ . . . as expected. echo -n " = " eval echo \$$param # Gives the *value* of variable. # ^^^^ ^^^ # The "eval" forces the *evaluation* #+ of \$$ #+ as an indirect variable reference. (( param ++ )) # On to the next. done exit $? # ================================================= $ sh echo-params.sh first second third fourth fifth Command-line parameter $1 = first Command-line parameter $2 = second Command-line parameter $3 = third Command-line parameter $4 = fourth Command-line parameter $5 = fifth |
例 15-14. 强制注销
#!/bin/bash # Killing ppp to force a log-off. # For dialup connection, of course. # Script should be run as root user. SERPORT=ttyS3 # Depending on the hardware and even the kernel version, #+ the modem port on your machine may be different -- #+ /dev/ttyS1 or /dev/ttyS2. killppp="eval kill -9 `ps ax | awk '/ppp/ { print $1 }'`" # -------- process ID of ppp ------- $killppp # This variable is now a command. # The following operations must be done as root user. chmod 666 /dev/$SERPORT # Restore r+w permissions, or else what? # Since doing a SIGKILL on ppp changed the permissions on the serial port, #+ we restore permissions to previous state. rm /var/lock/LCK..$SERPORT # Remove the serial port lock file. Why? exit $? # Exercises: # --------- # 1) Have script check whether root user is invoking it. # 2) Do a check on whether the process to be killed #+ is actually running before attempting to kill it. # 3) Write an alternate version of this script based on 'fuser': #+ if [ fuser -s /dev/modem ]; then . . . |
例 15-15. rot13 的一个版本
#!/bin/bash # A version of "rot13" using 'eval'. # Compare to "rot13.sh" example. setvar_rot_13() # "rot13" scrambling { local varname=$1 varvalue=$2 eval $varname='$(echo "$varvalue" | tr a-z n-za-m)' } setvar_rot_13 var "foobar" # Run "foobar" through rot13. echo $var # sbbone setvar_rot_13 var "$var" # Run "sbbone" through rot13. # Back to original variable. echo $var # foobar # This example by Stephane Chazelas. # Modified by document author. exit 0 |
这是另一个使用 eval 来求值复杂表达式的示例,该示例来自 YongYe 早期版本的 Tetris 游戏脚本。
eval ${1}+=\"${x} ${y} \" |
例 A-53 使用 eval 将 数组 元素转换为命令列表。
eval 命令出现在旧版本的 间接引用 中。
eval var=\$$var |
![]() | eval 命令可用于参数化 花括号展开。 |
![]() | eval 命令可能存在风险,通常在存在合理替代方案时应避免使用。一个eval $COMMANDS执行COMMANDS的内容,其中可能包含诸如 rm -rf * 之类的不愉快的事情。对未知人士编写的不熟悉的代码运行 eval 是在冒险。 |
set 命令更改内部脚本变量/选项的值。它的一个用途是切换 选项标志,这些标志有助于确定脚本的行为。它的另一个应用是重置脚本看到的作为命令结果的位置参数 (set `command`)。然后,脚本可以解析命令输出的 字段。
例 15-16. 将 set 与位置参数一起使用
#!/bin/bash # ex34.sh # Script "set-test" # Invoke this script with three command-line parameters, # for example, "sh ex34.sh one two three". echo echo "Positional parameters before set \`uname -a\` :" echo "Command-line argument #1 = $1" echo "Command-line argument #2 = $2" echo "Command-line argument #3 = $3" set `uname -a` # Sets the positional parameters to the output # of the command `uname -a` echo echo +++++ echo $_ # +++++ # Flags set in script. echo $- # hB # Anomalous behavior? echo echo "Positional parameters after set \`uname -a\` :" # $1, $2, $3, etc. reinitialized to result of `uname -a` echo "Field #1 of 'uname -a' = $1" echo "Field #2 of 'uname -a' = $2" echo "Field #3 of 'uname -a' = $3" echo \#\#\# echo $_ # ### echo exit 0 |
更多关于位置参数的乐趣。
例 15-17. 反转位置参数
#!/bin/bash # revposparams.sh: Reverse positional parameters. # Script by Dan Jacobson, with stylistic revisions by document author. set a\ b c d\ e; # ^ ^ Spaces escaped # ^ ^ Spaces not escaped OIFS=$IFS; IFS=:; # ^ Saving old IFS and setting new one. echo until [ $# -eq 0 ] do # Step through positional parameters. echo "### k0 = "$k"" # Before k=$1:$k; # Append each pos param to loop variable. # ^ echo "### k = "$k"" # After echo shift; done set $k # Set new positional parameters. echo - echo $# # Count of positional parameters. echo - echo for i # Omitting the "in list" sets the variable -- i -- #+ to the positional parameters. do echo $i # Display new positional parameters. done IFS=$OIFS # Restore IFS. # Question: # Is it necessary to set an new IFS, internal field separator, #+ in order for this script to work properly? # What happens if you don't? Try it. # And, why use the new IFS -- a colon -- in line 17, #+ to append to the loop variable? # What is the purpose of this? exit 0 $ ./revposparams.sh ### k0 = ### k = a b ### k0 = a b ### k = c a b ### k0 = c a b ### k = d e c a b - 3 - d e c a b |
调用不带任何选项或参数的 set 只会列出已初始化的所有 环境变量和其他变量。
bash$ set AUTHORCOPY=/home/bozo/posts BASH=/bin/bash BASH_VERSION=$'2.05.8(1)-release' ... XAUTHORITY=/home/bozo/.Xauthority _=/etc/bashrc variable22=abc variable23=xzy |
将 set 与--选项一起使用会显式地将变量的内容分配给位置参数。如果--后面没有变量,它会取消设置位置参数。
例 15-18. 重新分配位置参数
#!/bin/bash variable="one two three four five" set -- $variable # Sets positional parameters to the contents of "$variable". first_param=$1 second_param=$2 shift; shift # Shift past first two positional params. # shift 2 also works. remaining_params="$*" echo echo "first parameter = $first_param" # one echo "second parameter = $second_param" # two echo "remaining parameters = $remaining_params" # three four five echo; echo # Again. set -- $variable first_param=$1 second_param=$2 echo "first parameter = $first_param" # one echo "second parameter = $second_param" # two # ====================================================== set -- # Unsets positional parameters if no variable specified. first_param=$1 second_param=$2 echo "first parameter = $first_param" # (null value) echo "second parameter = $second_param" # (null value) exit 0 |
unset 命令删除 shell 变量,实际上将其设置为 null。请注意,此命令不影响位置参数。
bash$ unset PATH bash$ echo $PATH bash$ |
例 15-19. “取消设置” 变量
#!/bin/bash # unset.sh: Unsetting a variable. variable=hello # Initialized. echo "variable = $variable" unset variable # Unset. # In this particular context, #+ same effect as: variable= echo "(unset) variable = $variable" # $variable is null. if [ -z "$variable" ] # Try a string-length test. then echo "\$variable has zero length." fi exit 0 |
![]() | 在大多数情况下,未声明的变量和已被 取消设置的变量是等效的。但是, ${parameter:-default} 参数替换结构可以区分两者。 |
export [4] 命令使变量可用于正在运行的脚本或 shell 的所有子进程。export 命令的一个重要用途是在 启动文件 中,初始化 环境变量 并使其可供后续用户进程访问。
![]() | 不幸的是,无法将变量导出回父进程,导出回调用或调用脚本或 shell 的进程。 |
例 15-20. 使用 export 将变量传递给嵌入的 awk 脚本
#!/bin/bash # Yet another version of the "column totaler" script (col-totaler.sh) #+ that adds up a specified column (of numbers) in the target file. # This uses the environment to pass a script variable to 'awk' . . . #+ and places the awk script in a variable. ARGS=2 E_WRONGARGS=85 if [ $# -ne "$ARGS" ] # Check for proper number of command-line args. then echo "Usage: `basename $0` filename column-number" exit $E_WRONGARGS fi filename=$1 column_number=$2 #===== Same as original script, up to this point =====# export column_number # Export column number to environment, so it's available for retrieval. # ----------------------------------------------- awkscript='{ total += $ENVIRON["column_number"] } END { print total }' # Yes, a variable can hold an awk script. # ----------------------------------------------- # Now, run the awk script. awk "$awkscript" "$filename" # Thanks, Stephane Chazelas. exit 0 |
![]() | 可以在同一操作中初始化和导出变量,例如 export var1=xxx。 但是,正如 Greg Keraunen 指出的那样,在某些情况下,这可能与设置变量然后导出变量的效果不同。
|
![]() | 要导出的变量可能需要特殊处理。参见 例 M-2。 |
与 declare -r 相同,将变量设置为只读,或者实际上,设置为常量。尝试更改变量会失败并显示错误消息。这是 shell 模拟 C 语言的 const 类型限定符。
这个强大的工具解析传递给脚本的命令行参数。这是 Bash 模拟 getopt 外部命令和 C 程序员熟悉的 getopt 库函数。它允许将多个选项 [5] 和关联的参数传递和连接到脚本(例如脚本名 -abc -e /usr/local).
getopts 结构使用两个隐式变量。$OPTIND是参数指针 (OPTion INDex) 和$OPTARG(OPTion ARGument) 选项的(可选)关联参数。声明中选项名称后的冒号标记该选项具有关联的参数。
getopts 结构通常打包在 while 循环 中,该循环一次处理一个选项和参数,然后递增隐式$OPTIND变量以指向下一个。
![]() |
|
while getopts ":abcde:fg" Option # Initial declaration. # a, b, c, d, e, f, and g are the options (flags) expected. # The : after option 'e' shows it will have an argument passed with it. do case $Option in a ) # Do something with variable 'a'. b ) # Do something with variable 'b'. ... e) # Do something with 'e', and also with $OPTARG, # which is the associated argument passed with option 'e'. ... g ) # Do something with variable 'g'. esac done shift $(($OPTIND - 1)) # Move argument pointer to next. # All this is not nearly as complicated as it looks <grin>. |
例 15-21. 使用 getopts 读取传递给脚本的选项/参数
#!/bin/bash # ex33.sh: Exercising getopts and OPTIND # Script modified 10/09/03 at the suggestion of Bill Gradwohl. # Here we observe how 'getopts' processes command-line arguments to script. # The arguments are parsed as "options" (flags) and associated arguments. # Try invoking this script with: # 'scriptname -mn' # 'scriptname -oq qOption' (qOption can be some arbitrary string.) # 'scriptname -qXXX -r' # # 'scriptname -qr' #+ - Unexpected result, takes "r" as the argument to option "q" # 'scriptname -q -r' #+ - Unexpected result, same as above # 'scriptname -mnop -mnop' - Unexpected result # (OPTIND is unreliable at stating where an option came from.) # # If an option expects an argument ("flag:"), then it will grab #+ whatever is next on the command-line. NO_ARGS=0 E_OPTERROR=85 if [ $# -eq "$NO_ARGS" ] # Script invoked with no command-line args? then echo "Usage: `basename $0` options (-mnopqrs)" exit $E_OPTERROR # Exit and explain usage. # Usage: scriptname -options # Note: dash (-) necessary fi while getopts ":mnopq:rs" Option do case $Option in m ) echo "Scenario #1: option -m- [OPTIND=${OPTIND}]";; n | o ) echo "Scenario #2: option -$Option- [OPTIND=${OPTIND}]";; p ) echo "Scenario #3: option -p- [OPTIND=${OPTIND}]";; q ) echo "Scenario #4: option -q-\ with argument \"$OPTARG\" [OPTIND=${OPTIND}]";; # Note that option 'q' must have an associated argument, #+ otherwise it falls through to the default. r | s ) echo "Scenario #5: option -$Option-";; * ) echo "Unimplemented option chosen.";; # Default. esac done shift $(($OPTIND - 1)) # Decrements the argument pointer so it points to next argument. # $1 now references the first non-option item supplied on the command-line #+ if one exists. exit $? # As Bill Gradwohl states, # "The getopts mechanism allows one to specify: scriptname -mnop -mnop #+ but there is no reliable way to differentiate what came #+ from where by using OPTIND." # There are, however, workarounds. |
此命令从命令行调用时,会执行脚本。在脚本中,source 文件名加载文件文件名。Sourcing 文件(点命令)将代码 导入 到脚本中,附加到脚本(效果与 C 程序中的#include指令相同)。最终结果与 “sourced” 代码行物理存在于脚本主体中相同。这在多个脚本使用公共数据文件或函数库的情况下很有用。
例 15-22. “包含” 数据文件
#!/bin/bash # Note that this example must be invoked with bash, i.e., bash ex38.sh #+ not sh ex38.sh ! . data-file # Load a data file. # Same effect as "source data-file", but more portable. # The file "data-file" must be present in current working directory, #+ since it is referred to by its basename. # Now, let's reference some data from that file. echo "variable1 (from data-file) = $variable1" echo "variable3 (from data-file) = $variable3" let "sum = $variable2 + $variable4" echo "Sum of variable2 + variable4 (from data-file) = $sum" echo "message1 (from data-file) is \"$message1\"" # Escaped quotes echo "message2 (from data-file) is \"$message2\"" print_message This is the message-print function in the data-file. exit $? |
文件数据文件用于上面的 例 15-22。必须存在于同一目录中。
# This is a data file loaded by a script. # Files of this type may contain variables, functions, etc. # It loads with a 'source' or '.' command from a shell script. # Let's initialize some variables. variable1=23 variable2=474 variable3=5 variable4=97 message1="Greetings from *** line $LINENO *** of the data file!" message2="Enough for now. Goodbye." print_message () { # Echoes any message passed to it. if [ -z "$1" ] then return 1 # Error, if argument missing. fi echo until [ -z "$1" ] do # Step through arguments passed to function. echo -n "$1" # Echo args one at a time, suppressing line feeds. echo -n " " # Insert spaces between words. shift # Next one. done echo return 0 } |
如果 sourced 文件本身是一个可执行脚本,那么它将运行,然后将控制权返回给调用它的脚本。Sourced 可执行脚本可以使用 return 来达到此目的。
参数可以(可选地)作为 位置参数 传递给 sourced 文件。
source $filename $arg1 arg2 |
脚本甚至可以 source 自身,尽管这似乎没有任何实际应用。
例 15-23. 一个(无用的)source 自身的脚本
#!/bin/bash # self-source.sh: a script sourcing itself "recursively." # From "Stupid Script Tricks," Volume II. MAXPASSCNT=100 # Maximum number of execution passes. echo -n "$pass_count " # At first execution pass, this just echoes two blank spaces, #+ since $pass_count still uninitialized. let "pass_count += 1" # Assumes the uninitialized variable $pass_count #+ can be incremented the first time around. # This works with Bash and pdksh, but #+ it relies on non-portable (and possibly dangerous) behavior. # Better would be to initialize $pass_count to 0 before incrementing. while [ "$pass_count" -le $MAXPASSCNT ] do . $0 # Script "sources" itself, rather than calling itself. # ./$0 (which would be true recursion) doesn't work here. Why? done # What occurs here is not actually recursion, #+ since the script effectively "expands" itself, i.e., #+ generates a new section of code #+ with each pass through the 'while' loop', # with each 'source' in line 20. # # Of course, the script interprets each newly 'sourced' "#!" line #+ as a comment, and not as the start of a new script. echo exit 0 # The net effect is counting from 1 to 100. # Very impressive. # Exercise: # -------- # Write a script that uses this trick to actually do something useful. |
无条件终止脚本。[6] exit 命令可以选择接受一个整数参数,该参数作为脚本的 退出状态 返回给 shell。良好的做法是以exit 0结束除最简单脚本之外的所有脚本,表明成功运行。
![]() | 如果脚本在没有参数的情况下以 exit 终止,则脚本的退出状态是脚本中执行的最后一个命令的退出状态,不包括 exit。这等效于 exit $?。 |
![]() | exit 命令也可用于终止 子 shell。 |
此 shell 内建命令将当前进程替换为指定的命令。通常,当 shell 遇到命令时,它会 派生 一个子进程来实际执行该命令。使用 exec 内建命令,shell 不会派生,并且 exec 的命令会替换 shell。因此,当在脚本中使用时,它会在 exec 的命令终止时强制退出脚本。[7]
例 15-24. exec 的效果
#!/bin/bash exec echo "Exiting \"$0\" at line $LINENO." # Exit from script here. # $LINENO is an internal Bash variable set to the line number it's on. # ---------------------------------- # The following lines never execute. echo "This echo fails to echo." exit 99 # This script will not exit here. # Check exit value after script terminates #+ with an 'echo $?'. # It will *not* be 99. |
例 15-25. 一个 exec 自身的脚本
#!/bin/bash # self-exec.sh # Note: Set permissions on this script to 555 or 755, # then call it with ./self-exec.sh or sh ./self-exec.sh. echo echo "This line appears ONCE in the script, yet it keeps echoing." echo "The PID of this instance of the script is still $$." # Demonstrates that a subshell is not forked off. echo "==================== Hit Ctl-C to exit ====================" sleep 1 exec $0 # Spawns another instance of this same script #+ that replaces the previous one. echo "This line will never echo!" # Why not? exit 99 # Will not exit here! # Exit code will not be 99! |
exec 也用于 重新分配文件描述符。例如,exec <zzz-file替换stdin为文件zzz-file.
![]() | 的-exec选项到 find 是不是与 exec shell 内建命令相同。 |
此命令允许动态更改 shell 选项(参见 例 25-1 和 例 25-2)。它经常出现在 Bash 启动文件 中,但在脚本中也有其用途。需要 版本 2 或更高版本的 Bash。
shopt -s cdspell # Allows minor misspelling of directory names with 'cd' # Option -s sets, -u unsets. cd /hpme # Oops! Mistyped '/home'. pwd # /home # The shell corrected the misspelling. |
将 caller 命令放在 函数 内部会回显到stdout关于该函数的调用者的信息。
#!/bin/bash function1 () { # Inside function1 (). caller 0 # Tell me about it. } function1 # Line 9 of script. # 9 main test.sh # ^ Line number that the function was called from. # ^^^^ Invoked from "main" part of script. # ^^^^^^^ Name of calling script. caller 0 # Has no effect because it's not inside a function. |
caller 命令还可以从另一个脚本中 sourced 的脚本返回 调用者 信息。类似于函数,这是一个 “子例程调用”。
您可能会发现此命令在调试中很有用。
一个返回成功 (零) 退出状态 的命令,但什么也不做。
bash$ true bash$ echo $? 0 |
# Endless loop while true # alias for ":" do operation-1 operation-2 ... operation-n # Need a way to break out of loop or script will hang. done |
一个返回不成功 退出状态 的命令,但什么也不做。
bash$ false bash$ echo $? 1 |
# Testing "false" if false then echo "false evaluates \"true\"" else echo "false evaluates \"false\"" fi # false evaluates "false" # Looping while "false" (null loop) while false do # The following code will not execute. operation-1 operation-2 ... operation-n # Nothing happens! done |
类似于 which 外部命令,type cmd 标识 “cmd”。与 which 不同,type 是 Bash 内建命令。 type 的有用-a选项标识关键字和内建命令,并且还查找具有相同名称的系统命令。
bash$ type '[' [ is a shell builtin bash$ type -a '[' [ is a shell builtin [ is /usr/bin/[ bash$ type type type is a shell builtin |
type 命令可用于测试特定命令是否存在。
记录指定命令的 路径 名称 -- 在 shell 哈希表 [8] 中 -- 这样 shell 或脚本在后续调用这些命令时就不需要搜索 $PATH。当调用 hash 时不带任何参数,它只会列出已哈希的命令。-r选项重置哈希表。
bind 内建命令显示或修改 readline [9] 键绑定。
获取 shell 内建命令的简短用法摘要。这是 whatis 的对应命令,但用于内建命令。help 信息的显示在 Bash 的 版本 4 发行版 中得到了急需的更新。
bash$ help exit exit: exit [n] Exit the shell with a status of N. If N is omitted, the exit status is that of the last command executed. |
[1] | 正如 Nathan Coulter 指出的那样,“虽然派生进程是一项低成本操作,但在新派生的子进程中执行新程序会增加更多开销。” |
[2] | 一个例外是 time 命令,在官方 Bash 文档中被列为关键字(“保留字”)。 |
[3] | 请注意,let 不能用于设置 字符串 变量。 |
[4] | 导出 信息是为了使其在更广泛的上下文中可用。另请参阅 作用域。 |
[5] | 选项 是充当标志的参数,用于打开或关闭脚本行为。与特定选项关联的参数指示选项(标志)打开或关闭的行为。 |
[6] | 从技术上讲,exit 仅终止运行它的进程(或 shell),而不是 父进程。 |
[7] | 除非使用 exec 来 重新分配文件描述符。 |
[8] | 哈希 (Hashing) 是一种为存储在表中的数据创建查找键的方法。数据项本身使用多种简单的数学 算法(方法或配方)之一进行 “加扰” 以创建键。 哈希 (hashing) 的一个优点是速度快。一个缺点是可能发生 冲突 (collisions) -- 其中单个键映射到多个数据项。 |
[9] |