linux shell要点与技巧

来源:互联网 发布:淘宝爆款打造小技巧 编辑:程序博客网 时间:2024/06/05 22:32

1. shell 中命令或者表达式返回0表示true,非0表示false。


2. 几个常用的环境变量:$HOME -- 用户目录         $PATH -- 默认可执行路径            $PWD -- 当前目录     $HOSTNAME -- 主机名          $RANDOM -- 返回一个0到32767之间的随机整数     $BASH_VERSION -- bash版本号 


3. Bash中有三种类型的变量:string,integer,array。

创建数组:

创建数组的方法就是圆括号:array=(a b c),还可以指定下标:array=( [0]=a [1]=b [10]=c [20]=d)得到一个稀疏数组。获取一个文件名数组(永远不要在脚本中使用ls命令来获取文件名,请使用通配符!)

$ files=$(ls)    # BAD, BAD, BAD!$ files=($(ls))  # STILL BAD!
$ files=(*)      # Good! No word splitting problem.
+=() 语法可以将一个或多个值加入已有的数组中。如 files+=( "file1"  "$file"  "${names[@]}" )。

数组的用法:

${array[i]}可以引用数组变量array下标为i的元素,如果下标为i的元素不存在则返回空。${array[@]}可以获取数组中所有元素,通常用于遍历数组。${array[*]}得到数组的字符串表示,即所有元素用分隔符(默认空格,可通过设置IFS变量的值改变)相连组成的字符串${#array[@]}获得数组长度,即数组中元素数。

for name in "${names[@]}"; do echo "$name"; done #记得加双引号!!

$ ( IFS=,; echo "Today's contestants are: ${names[*]}" )  #不能用${names[@]},IFS=,;必须有分号Today's contestants are: Bob,Peter,lhunath,Big Bad John

printf语句可以很优雅的打印数组中的所有元素,至于为什么它会有循环的功能,I don't know either.

$ printf "%s\n" "${names[@]}"

数组还有个很好的性质:index=1; echo "${array[index]}"; 可以直接使用变量index(当然$index也是ok的)获取下标为1的元素,而且可以在[]中进行算术运算,echo "${array[index + 2]}"; 。

关联数组Associative Array(你可以使用字符串进行索引,有点像unordered map):

$ declare -A fullNames$ fullNames=( ["lhunath"]="Maarten Billemont" ["greycat"]="Greg Wooledge" )
$ for user in "${!fullNames[@]}" #顺序是不定的,${!array[@]}得到所有key,${array[@]}得到所有value> do echo "User: $user, full name: ${fullNames[$user]}."; done

4.  [[ ]]关键字。[[ ]]关键字可以用于测试,比[ ] 和test方便很多,完全可以取代它们。在[[ ]]中可以直接使用 && || 组合逻辑表达式,直接使用各种比较运算符进行字符串的比较(包括=进行通配符匹配,=~进行正则表达式匹配,数字比较请使用(( ))),而且不必担心其中变量展开的word splitting问题(指值含有空格的变量在使用时若不加双引号会造成错误,比如foo="ab cd", [ $foo = "ab cd" ] 会出现命令错误)。需要注意的一点是,[[ ]]中出现在右侧的变量不加双引号时允许通配符扩展,加双引号时则不允许。但总之,如果你不确定,请给你的变量引用加上双引号,你出错的概率会最小。例:

$ foo=[a-z]* name=lhunath$ [[ $name = $foo   ]] && echo "Name $name matches pattern $foo"Name lhunath matches pattern [a-z]*$ [[ $name = "$foo" ]] || echo "Name $name is not equal to the string $foo"Name lhunath is not equal to the string [a-z]*


5. 如果要把含有空格的变量作为某个命令的参数,一定要把这个变量用双引号括起来word splitting问题)。比如a='2013-04-07 16:25' ,命令 date +%s -d $a 会报错date: extra operand `16:25',因为$a展开后被命令当成2个参数了。正确的方法是:date +%s -d “$a”。


6. 命令组合。你可以通过 && 或者 || 把不同的命令组合起来作为 if then... else ... fi 的简写,比如 cmd1 && cmd2 || cmd3 。最正宗的命令组合是{ cmd; cmd; } 的形式,注意花括号两边有空格而且最后一个分号不能少。当然,使用逻辑符号和花括号这两种形式可以结合使用。输入重定向也可用于命令组合,如:

{    read firstLine    read secondLine    while read otherLine; do        something    done} < file


7. 通配符与正则表达式并不是一回事,前者用来匹配文件名,后者用来匹配模式。基本的通配符包括 * ? [...] 三种,分别表示匹配任意多字符、0或1个字符、括号内的某个字符;扩展的通配符可以将若干通配符组合,包括   *(list)      ?(list)   +(list)   @(list)   !(list)  5种,分别表示任意多、0或1个、至少1个、1个、非,list表示若干通配符的组合,用 & 或 | 相连,比如:

$ lsnames.txt  tokyo.jpg  california.bmp$ echo !(*jpg|*bmp)names.txt
bash中经常要用 shopt -s extglob 命令开启扩展通配符后才能使用。通配符也可以用来进行变量值的比较

$ filename="somefile.jpg"$ if [[ $filename = *.jpg ]]; then> echo "$filename is a jpeg"> fisomefile.jpg is a jpeg

这是通配符唯一不是匹配文件名的用法。通配符是直接使用的,不要加(双)引号。


8. 正则表达式不要加引号,这在bash3.2之后是强制要求。所以,可以直接使用正则表达式,或者将模式串保存在变量中,再使用变量,如:

$ langRegex='(..)_(..)'$ if [[ $LANG =~ $langRegex ]]> then>     echo "Your country code (ISO 3166-1-alpha-2) is ${BASH_REMATCH[2]}.">     echo "Your language code (ISO 639-1) is ${BASH_REMATCH[1]}."> else>     echo "Your locale was not recognised"> fi

9. 花括号扩展。花括号扩展只有两种形式,{a,b}c 扩展至 ac bc,{1..4} 0 扩展至 10 20 30 40。花括号扩展在通配符扩展之前,用来生成单词,不能用来匹配

$ echo th{e,a}nthen than$ echo {/home/*,/root}/.*profile/home/axxo/.bash_profile /home/lhunath/.profile /root/.bash_profile /root/.profile$ echo {1..9}1 2 3 4 5 6 7 8 9$ echo {0,1}{0..9}00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19

10. 管道命令都是在单独的子shell中执行的,其中的变量不会传递给外部。因此,试图通过管道命令计算全局变量是行不通的。把若干个命令用圆括号括起来也表示在子shell中执行:( cmd; cmd; )。解决这类问题的方法有进程替换(process substitution),其形式为<(cmds;)  或 >(cmds;),前者通常用其输出作为其它命令的输入,后者则是用其它命令的输出作为它的输入。

# Working example, using bash syntax.i=0while read line; do  ((i++))  ...done < <(sort list1)echo "$i lines processed"


11. 交互式shell中正确输入 '\t' 的方法是:按Ctrl+v,然后再按Tab。但是脚本中Ctrl+v再按Tab则会自动转化成空格,这时要使用$'\t',sort -t$'\t' -k3,3 file才可以。Ctrl+d输入EOF标志可用于结束输入。


12. 如果你想要判断if [[ $var = foo || $var == bar || $var = more ]] 而又不想写很多次$var,可以使用case语句

# bash   case "$var" in      foo|bar|more) ... ;;   esac

case中的每个匹配项可以使用通配符而不是正则表达式。


13. 如何处理命令行参数。POSIX标准要求参数选项和参数值之间必须有空格,即-f filename,bash和dash允许没有空格-ffilename。


14. 子进程是无法修改父进程环境的,包括父进程中的变量、当前工作目录、打开的文件描述符、资源限制等等。因此,调用一个脚本改变PWD是不会成功的,这个脚本会在子shell中执行,对当前shell没有任何影响。让脚本在当前shell中执行有两种方式:source命令或者“.”

echo 'cd /tmp' > "$HOME/mycd"  # Create a file that contains the 'cd /tmp' command.. $HOME/mycd     # Source that file, executing the 'cd /tmp' command in the current shell.pwd              # Now, we're in /tmp


15. 重定向。 当你的命令设计从stdin读数据,而现在你需要从文件中读数据,那么请使用重定向。使用cat或者管道是个糟糕的选择,因为它需要创建新的进程在子shell中执行。

{    some commands} >msg.log 2>&1       #或者使用简写 &>msg.log,将stdout和stderr都重定向到msg.log
# Keep both stdout and stderr unmolested.   exec 3>&1 4>&2   foo=$( { time bar 1>&3 2>&4; } 2>&1 )  # Captures time only.   exec 3>&- 4>&-


16. HereDoc和HereString。可以用来将一段文字或者一个变量的值重定向到stdin。

cat <<'XYZ'  #这段HereDoc的名字为XYZ,因为名字被单引号括起来了,所以其内容都作为普通字符,变量不会扩展。My home directory is $HOMEXYZ#输出:My home directory is $HOME。如果名字不用单引号括起来,$HOME会扩展。
$ grep proud <<<"$USER sits proudly on his throne in $HOSTNAME."  #HereStringlhunath sits proudly on his throne in Lyndir.
HereDoc和HereString可在同一行跟其它重定向连用。

$ cat <<EOF > file> My home dir is $HOME> EOF

17. 将stderr重定向到管道。

exec 3>&1                       # Save current "value" of stdout.myprog 2>&1 >&3 | grep ...      # Send stdout to FD 3.exec 3>&-                       # Now close it for the remainder of the script.

18. 删除变量、函数或者别名。unset 的 -f  -v 参数可缺省,缺省时函数优先。

$ unset -f myfunction

$ unset -v 'myArray[2]' # unset element 2 of myArray. The quoting is important to prevent globbing.

$ unalias rm



19. 作业控制。

  • Ctrl-Z sends SIGTSTP to the foreground job (usually suspending it)暂停

  • Ctrl-C sends SIGINT to the foreground job (usually terminating it)终止

  • Ctrl-\ sends SIGQUIT to the foreground job (usually causing it to dump core and abort)

暂停当前正在执行的作业后,可用jobs命令查看:

  • jobs [options] [jobspec ...]: 列出所有后台作业和暂停的作业. Options 包括 -p (list process IDs only), -s (list only suspended jobs), and -r (list only running background jobs). 

参数jobspec 可以是:

  • %n to refer to job number n.

  • %str to refer to a job which was started by a command beginning with str. 没有则出错。

  • %?str to refer to a job which was started by a command containing str. 没有则出错。

  • %% or %+ to refer to the current job: the one most recently started in the background, or suspended from the foreground. fg and bg will operate on this job if no jobspec is given.

  • %- for the previous job (the job that was %% before the current one).

kill 命令可用JobID直接终止一个作业,而不用找到其PID。disown 将一个作业从作业队列中移除。
  • kill can take a jobspec instead of a process ID.

  • disown tells bash to forget about an existing job. This keeps bash from automatically sending SIGHUP to the processes in that job, but also means it can no longer be referred to by jobspec.

fg 命令可将某个作业取到前端执行,bg 命令可将某个作业放到后台执行,suspend 命令挂起当前shell。
  • fg [jobspec]: bring a background job to the foreground.

  • bg [jobspec ...]: run a suspended job in the background.

  • suspend: suspend the shell (mostly useful when the parent process is a shell with job control).


20. 进程管理。
$ echo $$ $!        #$$得到当前进程id,$!得到上个命令对应进程的id
根据命令名字获取进程ID。在脚本中请使用$!保存你关注的子进程id,不要使用下面命令去获取pgrep name #获取包含name的命令的pidps aux | grep -v grep | grep nameps aux | grep [n]ame   #the easiest way
kill -0 $pid #测试$pid的进程是否存在,若存在则返回true

21. 读取文件行到数组。
# Read all the files (from a text file, 1 per line) into an array.IFS=$'\n' read -r -d '' -a files < inputlist

22. 脚本中有管道命令,想让脚本被kill时同时结束管道命令。
通过作业控制 set -m 来处理,将管道命令当做一个整体来看待。
#!/bin/bashset -mtrap 'kill %%' EXIT    #设置脚本终止时的自陷程序,%%指当前作业号即最后一个后台执行的管道命令command1 | command2 &wait    #等待子进程结束,还可以指定pid。如 wait $pid; exitcode=$? 等待特定的子进程结束并获取其退出码。

23. 父子进程的关系,进程PID的回收。
    
    如果父进程没有结束,那么它的子进程的PID就不会被回收,不论子进程还活着或是已经僵死,直到父进程调用wait等待子进程结束。但是如果父进程终止了,你就无从知道子进程的状态了,子进程结束后它的PID可能被分配给新的进程,这时你kill $childPID,就把新进程误杀了。If the parent process manages its child process, it can be absolutely certain that, even if the child process dies, no other new process can accidentally recycle the child process's PID until the parent process has waited for that PID and noticed the child died. This gives the parent process the guarantee that the PID it has for the child process will ALWAYS point to that child process, whether it is alive or a "zombie". Nobody else has that guarantee.

    子进程从父进程继承很多东西:包括打开的文件描述符,环境变量,用户(组)ID,系统资源限制,和umask

   A child process inherits many things from its parent:

  • Open file descriptors. The child gets copies of these, referring to the same files.
  • Environment variables. The child gets its own copies of these, and changes made by the child do not affect the parent's copy.

  • Current working directory. If the child changes its working directory, the parent will never know about it.

  • User ID, group ID and supplementary groups. A child process is spawned with the same privileges as its parent. Unless the child process is running with superuser UID (UID 0), it cannot change these privileges.
  • System resource limits. The child inherits the limits of its parent. A process that runs as superuser UID can raise its resource limits (setrlimit(2)). A process running as non-superuser can only lower its resource limits; it can't raise them.

  • umask.

24. 权限问题。
    umask 决定了你创建一个新的文件或者文件夹时不具备哪些权限。每个进程都有umask,最初继承自父进程,可由当前进程修改。如果umask是022,对于新文件来说,权限就是644,对于新文件夹来说就是755,文件夹权限默认可执行。
    文件夹的r权限,使得用户可以echo dir/*得到文件夹中的文件及子文件夹,但是如果没有x权限则无法对其中的文件做任何操作,无法ls获得文件的属性和cd到该文件夹;w权限使得用户可在文件夹中创建、删除、重命名文件,删除和重命名与文件的owner及权限无关;x权限通常跟r权限相同,但是也有一种特殊情况:只有x权限没有r权限,比如ftp文件夹权限设置为711,owner希望上传者可以直接访问他上传的文件,但是不能看到文件夹中还有哪些文件。

24.  替换文件中的特定字符串。
sed -i 's/old/new/g' ./*  # GNU,最方便的方法,-i参数表示在副本中修改并替换原文件,相当于直接修改原文件
    使用vi或者vim的非可视化版本ex,使用man ex 可看到你系统中的ex对应vi还是vim。
ex -sc '%s/old/new/g|x' file
   在vim中,如果没有old字符串,程序会卡住,需要使用e参数来忽略错误。
ex -sc '%s/old/new/ge|x' file
   vim中,还可以使用argdo参数来减少ex进程的数量。
# Bash 4+ ex -sc 'argdo %s/old/new/ge|x' file*  #处理以file开头的文件

25. 判断字符串是否是数字串。
# Bash. 使用扩展的通配符shopt -s extglob[[ $foo = *[0-9]* && $foo = ?([+-])*([0-9])?(.*([0-9])) ]] &&  echo "float number"
# Bash. Put the RE in a var for backward compatibility with versions <3.2regexp='^[-+]?[0-9]*(\.[0-9]*)?$'  #使用正则表达式if [[ $foo = *[0-9]* && $foo =~ $regexp ]]; then    echo "'$foo' looks rather like a number"else    echo "'$foo' doesn't look particularly numeric to me"fi

26. bash各个版本增加的新特性。

27. 删除变量值前后的空白字符。
# Bash   shopt -s extglob   var="${var##*( |$'\t'|$'\n')}"   # trim the left   var="${var%%*( |$'\t'|$'\n')}"   # trim the right
    或者使用read和HereString,以空字符串(相当于NUL)做分隔符。会忽略前后空白,将中间的部分赋值给x。
# Bash   read  -rd '' x <<< "$x"