郑重声明,这个文章不是我写的,是我的同事——刘彬做的学习笔记。我觉得写的太好了,但他又没有发到网上,只好自己贴一下了。所以转载必须带上原作者的名字,以及本段声明。
Contents
此文分享我在bash学习,使用,编程中的碰到的一些问题,以及一些体会。
CTRL 键相关的快捷键:
ALT 键相关的快捷键:
可通过bash内键命令 bind 修改按键绑定,具体可查帮助: help bind
对于使用readline库的程序,可修改 /etc/inputrc 或 ~/.inputrc 修改绑定关系。
ps: 使用vim的建议交换 CapsLock 与 Esc 键
我的 ~/.inputrc
"\e[A": history-search-backward "\e[B": history-search-forward "\C-p": history-search-backward "\C-n": history-search-forward # mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving "\e[1;5C": forward-word "\e[1;5D": backward-word "\e[5C": forward-word "\e[5D": backward-word "\e\e[C": forward-word "\e\e[D": backward-word
# 忽略重复的命令 export HISTCONTROL=ignoredups # 忽略由冒号分割的这些命令 export HISTIGNORE="[ ]*:&:bg:fg:exit" export HISTFILESIZE=1000000000 export HISTSIZE=1000000 export HISTFILE=~/.lb_bash_history shopt -s histappend
ls
eval "`dircolors -b ~/.dircolors`" alias ls='ls --color=auto'
grep, fgrep, egrep
export GREP_OPTIONS='--color=auto'
man
export LESS_TERMCAP_mb=$'\E[01;31m' export LESS_TERMCAP_md=$'\E[01;31m' export LESS_TERMCAP_me=$'\E[0m' export LESS_TERMCAP_se=$'\E[0m' export LESS_TERMCAP_so=$'\E[01;44;33m' export LESS_TERMCAP_ue=$'\E[0m' export LESS_TERMCAP_us=$'\E[01;32m'
if [ -f /etc/bash_completion ]; then . /etc/bash_completion fi
中文目录拼音补全: chsdir
介绍怎样编写补全脚本: An introduction to bash completion part 1 , An introduction to bash completion part 2
Bash completion for discoverer and retriever
function _discoverer() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-r --home -v --version -h --help -w --website -k --keyword -K --linksite-keyword \ -c --category --related -u --userid -m --max-videos -M --max-pages \ -F --linksite-fullsite -C --linksite-category --log-level" case "$prev" in -w | --website) local running="youtube dailymotion myspace aol_movie metacafe msn tudou veoh yahoo youku megavideo ku6 s56 pomoho room6 pandora salloumi nicovideo myvideo tu 66stage blogspot campusist chewymovies fastpasstv freetvonline legalmovies mediateevee movie6 moviesondemand movierumor moviesdesk moviesforfree moviesister movietvonline mymovies nabolister omegatube quickss sevenscreen sidereel tvdash tvpapa watchnewmovies watchmovies watchtvsitcoms wizmovies xoxomovie live-video msn-video sogou-video bing-video-cn bing-video-com baidu-video google-video-cn google-video-com gougou-video soso-video alluc nabolister tvshack tvlinks watchmoviesfree taringa thepiratecity cinemawiki joox tvpapa familyguy freeonlineepisode moviealien tubezoom videofactor redcurtainmovies findthatshow watcheveryshow watchgossipgirl bynik watchtvsitcoms likestreaming allomovies alloshowtv diziport surfthechannel delatv estrenosonline momomesh movie2k ls2megaupload yesfilmes ninjavideo yidio casttv videosurf" running+="youporn slutload xnxx gaywatch megaporn xvideos itsallgay tube8 tnaflix keezmovies xhamster gayforit redtube empflix pornhub jerkyourtube youporngay" COMPREPLY=( $(compgen -W "$running" -- "$cur") ) return 0 ;; -r | --home) local running="$(dirname $PWD)" COMPREPLY=( $(compgen -W "$running" -- "$cur") ) return 0 ;; *) ;; esac if [[ ("$cur" == -*) || ( ("$cur" = "") && ("$prev" != -*) ) ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) fi } complete -F _discoverer discoverer function _retriever() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-r --home -v --version -h --help -d --detail -D --download -k --keyid --removal --resource --log-level" opts+=" --need-multi-clip --max-total-size --max-clip-num" case "$prev" in -r | --home) local running="$(dirname $PWD)" COMPREPLY=( $(compgen -W "$running" -- "$cur") ) return 0 ;; *) ;; esac if [[ ("$cur" == -*) ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) fi } complete -F _retriever retriever
脚本程序通常第一行以 #! 开头指定一个脚本解释器。如
#!/bin/bash #!/bin/sh #!/usr/bin/awk -f #!/bin/sed -f #!/usr/bin/expect -f #!/usr/bin/env perl
注意 : linux 里面,解释器之后只能有一个参数,如有多个,也只做为一个。
例子: test.pl
#!/usr/bin/env LC_CTYPE=zh_CN.UTF-8 perl print "hello\n"
perl test.pl 可以正常运行,./test.pl则不会正常执行,且不会退出。
运行 ./test.pl 相当于 /usr/bin/env "LC_CTYPE=zh_CN.UTF-8 perl" ./test.pl 。 evn会先设置LC_CTYPE="zh_CN.UTF-8 perl"这样一个环境变量,然后执行 ./test.pl,这就回到了最开始,于是一不断重复这个过程,不退出。
ps: 关于脚本程序第一行的相关信息可见于:man execve
Bash 有以下保留字:
! case do done elif else esac fi for function if in select then until while { } time [[ ]]
注意:
ps: 可用 type 查看是否为保留字,内建命令等
A parameter is an entity that stores values. A variable is a parameter denoted by a name.
指 $1, $2, $3 等
注意: $0, $1, .. $9 没有问题,$10 将被解释为 ${1}0。要使用 >= 10 的位置参数,需用 { } 括起来,如 ${15}。
From bash manual
The shell treats several parameters specially. These parameters may only be referenced; assignment to them is not allowed. * Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable. If IFS is unset, the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening separators. @ Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$@" is equivalent to "$1" "$2" ... If the double-quoted expansion occurs within a word, the expansion of the first parameter is joined with the beginning part of the original word, and the expansion of the last parameter is joined with the last part of the original word. When there are no positional parameters, "$@" and $@ expand to nothing (i.e., they are removed). # Expands to the number of positional parameters in decimal. ? Expands to the exit status of the most recently executed foreground pipeline. - Expands to the current option flags as specified upon invocation, by the set builtin command, or those set by the shell itself (such as the -i option). $ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell. ! Expands to the process ID of the most recently executed background (asynchronous) command. 0 Expands to the name of the shell or shell script. This is set at shell initialization. If bash is invoked with a file of commands, $0 is set to the name of that file. If bash is started with the -c option, then $0 is set to the first argument after the string to be executed, if one is present. Otherwise, it is set to the file name used to invoke bash, as given by argument zero. _ At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list. Subsequently, expands to the last argument to the previous command, after expansion. Also set to the full pathname used to invoke each command exe cuted and placed in the environment exported to that command. When checking mail, this parameter holds the name of the mail file currently being checked.
使用较多的有 $*, $@, $#, $?, $$, $!, $0
* 与 @ 的区别,在别的地方法了是这样。如在数组中 "${array[*]}" 与 "${array[@]}" 。
if list; then list; [ elif list; then list; ] ... [ else list; ] fi
这是最常用的分支语句,通常用 [ ], [[ ]], (( )) 进行条件测试。事实上一切语句都可以用,如
if echo "$var" | tr '[:upper:]' '[:lower:]' | grep -q 'regex expression' ; then echo matched fi
case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
case 使用 pattern 匹配进行测试,变得非常好用。 如检查一个字符串是否包含别一个字符串
case "$1" in ) *"$2"* ) true ;; * ) false ;; esca
&&, ||
如 [ $# -ne 2 ] && exit 1 或 [ $# -eq 2 ] || exit 1
for name [ in word ] ; do list ; done
for host in $(<host_list) ; do ... ; done for i in {1..100} ; do ... ; done for f in /aaa/bbb/cc* ; do ... ; done for v in "$@" ; do ... ; done
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
跟 c/c++ 中 for 语义一致,好用。如
for (( i=0; i<100; i++ )) ; do ... ; done
while list; do list; done
while true ; do ...; done while read line ; do ...; done < input_file
until list; do list; done
语义跟while相反,没见人用过。
$ echo abc{d,e,f} abcd abce abcf $ echo {a,b,c}{d,e,f} ad ae af bd be bf cd ce cf $ echo {1..10} 1 2 3 4 5 6 7 8 9 10 $ echo {1..10..2} 1 3 5 7 9
ps:
cp filename{,.bak} 好像是某个网站选出来的十大最酷的linux单行命令之一。
$ echo ~ /home/liubin $ echo ~liubin /home/liubin $ echo ~tracker /home/tracker $ echo ~no ~no $ echo ~+ /tmp $ echo ~- /home/liubin
最基本的
根据parameter未定义或值为空进行操作/扩展
${parameter:-word}
${parameter:=word}
${parameter:?word}
${parameter:+word}
较为有用,可用于为输入参数置默认值等。如
out_file=${1:-/dev/null}
size=$(stat -c %s $file) if [ ${size:-0} -lt 100 ] ; then ... fi
数组相关
字符串操作
${#parameter}
${parameter:offset}
${parameter:offset:length}
${parameter#word}
${parameter##word}
${parameter%word}
${parameter%%word}
非常有用,如 basename 可近似于 ${path##*/} , dirname 可近似于 ${path$/*}
${parameter/pattern/string}
${parameter^pattern}
${parameter^^pattern}
${parameter,pattern}
${parameter,,pattern}
上面四个大小写转换的应该是在 bash 4.0 中引入的。写入脚本中要慎重。
另外
$(command) or `command`
会去掉最后的newline,但中间的会保留。
var=$(<file) 跟var=$(cat file) 是相同的,但要快。
``与$()处理 '\' 的方式是不同的。
`'sed -e 's/\\/\\\\/g'` 是不可用的,而 $(sed -e 's/\\/\\\\/g') 是可用的
$ echo "`echo '\\'`" \ echo "$(echo '\\')" \\ echo "`echo sed -e 's/\\/\\\\/g'`" sed -e s/\/\\/g $ echo "$(echo sed -e 's/\\/\\\\/g')" sed -e s/\\/\\\\/g
使用``嵌套时要将里面的 `` 转义。
``是比较旧的方式,现已渐被$()替换
ps: 个人认为 `` 写在脚本中也不好看,符号太小,且易与 ' 混淆,建议不要使用。
$(( expression ))
<(list) or >(list)
Process substitution is supported on systems that support named pipes (FIFOs) or the /dev/fd method of naming open files.
例子
$ diff <(printf "%s\n" a b c) <(printf "%s\n" a b c) $ diff <(printf "%s\n" a b c) <(printf "%s\n" a bb c) 2c2 < b --- > bb $ paste <(printf "%s\n" a b c) <(printf "%s\n" a bb c) a a b bb c c
可用于将 list | while read l ; do ...; done 替换为 while read l ; do ...; done < <(list) 这种形式,从而避免 while 循环于子 shell 执行。
通过 pattern matching 进行扩展。
From bash manual
* Matches any string, including the null string. When the globstar shell option is enabled, and * is used in a filename expansion context, two adja cent *s used as a single pattern will match all files and zero or more directories and subdirectories. If followed by a /, two adjacent *s will match only directories and subdirectories. ? Matches any single character. [...] Matches any one of the enclosed characters. A pair of characters separated by a hyphen denotes a range expression; any character that sorts between those two characters, inclusive, using the current locale's collating sequence and character set, is matched. If the first character following the [ is a ! or a ^ then any character not enclosed is matched. The sorting order of characters in range expressions is determined by the current locale and the value of the LC_COLLATE shell variable, if set. A - may be matched by including it as the first or last character in the set. A ] may be matched by including it as the first character in the set. Within [ and ], character classes can be specified using the syntax [:class:], where class is one of the following classes defined in the POSIX stan dard: alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit A character class matches any character belonging to that class. The word character class matches letters, digits, and the character _. Within [ and ], an equivalence class can be specified using the syntax [=c=], which matches all characters with the same collation weight (as defined by the current locale) as the character c. Within [ and ], the syntax [.symbol.] matches the collating symbol symbol.
路径扩展是在单词割之后进行,所以如路径名中有空格,该路径也只会做为一个参数传入。
所以,一般来说这种写法 for f in $(ls $dir) ; do ... ; done 不如 for f in $dir/* ; do ...; done
After the preceding expansions, all unquoted occurrences of the characters , ', and " that did not result from one of the above expansions are removed.
$ var= $ [ -z "$var" ] ; echo $? 0 $ [ -n "$var" ] ; echo $? 1 $ [ -z $var ] ; echo $? 0 $ [ -n $var ] ; echo $? 0
在用 -n 进行字符串不为空的测试时,一定要将所测试的变量用 "" 引起来。
bash 一般的做法是通过外部命令来实现,或自己写个函数来做。
在较高版本的bash中可用以下方法
$RANDOM 产生 0 - 32767 之间随机数,要产生更大的随要数,可以以 $RANDOM 为基础做一个函数。 也可以像 hexdump -e '"%u\n"' -n 4 /dev/random 这样去读 /dev/random
read 读一行时会对 \ 转义,一般来说是没用的,需加上 -r 参数。
read时会去掉头尾的空白,如需要保留,则read后不要跟变量,用 $REPLY 访问读到的内容。
如对一个文本文件进行通过read进行复制可以: while read -r ; do echo "$REPLY" ; doen <input >output
需防止 while 中的命令去读 stdin
如
printf "%s\n" localhost localhost | while read host ; do ssh $host whoami ; done
这样是有问题的,正确的方法可为
printf "%s\n" localhost localhost | while read host ; do ssh -n $host whoami ; done printf "%s\n" localhost localhost | while read host ; do ssh $host whoami </dev/null ; done exec 3< <(printf "%s\n" localhost localhost) while read -u3 host ; do ssh $host whoami ; done exec 3<&-
见: http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F
$cmd 一般会有问题,大概是这样理解的 :
$cmd 会最先做一个syntax parser,然后再参数扩展,然后会到 word splitting。 cmd='echo "a b"' , $cmd 在word splitting后会变成 echo '"a' 'b"'
要只被交到 word splitting 的就是按IFS来分的了,如
$ echo $(echo '"a b"') "a b" $ echo "$(echo '"a b"')" "a b"
所以会被分成那个鸟样。
如cmd中有参数,那么 "$cmd" 这样直接执行一般不会有结果,返回值是127,就是找不到命令。
而 eval 是相当于对 扩展好的 cmd 再走了一下解释流程,所以没有问题。
最后结论:不要用 $cmd 要用 eval "$cmd"
不要用 local a="a_value" b="${a} + b value"
因为 local 是个命令,跟bash中赋值语句的执行是不一样的。
例子
$ cat t.sh #!/bin/bash function t1() { local a=t1 b=$a.bb echo "a:[$a] b:[$b]" } function t2() { a=t2 b=$a.bb echo "a:[$a] b:[$b]" } t1 t2 t1 $ ./t.sh a:[t1] b:[.bb] a:[t2] b:[t2.bb] a:[t1] b:[t2.bb]
同理,export也是一样的。
命令前有赋值(如 PATH=/usr/bin env ),那个这个赋值只对这个命令本身有效,不会影响当前环境。 所以这个赋值一般要是环境变量才行,不然应该一点用都没有。
常用的形式有 IFS=: read v1 v2 v3
echo hello # stdout echo hello 1>&2 # stderr
上面是必需必需的, 还可以通过重定向到以下文件来指定:
/dev/stdout /dev/stderr
/dev/fd/1 /dev/fd/2
/proc/$$/fd/1 /proc/$$/fd/2
/proc/self/fd/1 /proc/self/fd/2
子shell中的变量对于子shell之外的代码块来说, 是不可见的. 当然, 父进程也不能访问这些变量。
(command;command;...; command)
可用于进入一个目录操作再退出
( cd $dir .... # do something )
From bash manual
Each command in a pipeline is executed as a separate process (i.e., in a subshell)
常见的一个问题
num=1 seq 1 5 | while read l ; do num=$((num+1)); done echo $num
可使用进程替换解决
num=1 while read l ; do num=$((num+1)); done < <(seq 1 5) echo $num
不支持进程替换的可用命名管道解决
mkfifo t_fifo seq 1 5 >t_fifo & num=1 while read l ; do num=$((num+1)); done <t_fifo echo $num
$ a=aa $ echo $(a=bb;echo $a) bb $ echo $a aa
fork炸弹,造成死机,危险。
解释
:() { : | : & } :
定义了一个名为 ':' 的递归函数,然后调用。
ps: ':' 是一个啥事不干的内建命令,可用于占位,如
if [ $v1 -lt v2 ] ; then : else echo do something fi
本文出自 “passover【毕成功的博客】” 博客,转载请与作者联系!