GUI(Graphical User Interface 图形用户界面);
CLI(command-line interface,命令行界面);
shell不单单是一种CLI。它是一个时刻都运行的复制交互式程序。输入命令并利用shell来运行脚本会出现一些即有趣又令人困惑的问题。搞清楚shell进程以及它与系统之间的关系能够帮助你解决这些难题。
1. shell的类型
ls -l /bin/bash
ls -l /bin/tcsh //源自最初的C shell
ls - l /bin/dash //ash shell的Debian版本
ls -l /bin.csh //C shell的软连接指向的是tcsh shell
这些shell程序都可以被设置为用户默认的shell。不过由于bash shell的广为流传,很少有人使用其他的shell作为默认的shell。
默认的交互shell会在用户登录某个虚拟控制台终端或者GUI运行中运行终端仿真器时启动。不过还有另外一个默认的shell 是/bin/sh,它作为默认的系统shell,用于那些需要在启动时使用的系统shell脚本。
ls -l /bin/sh //能看出 当前默认的shell是否被软连接将默认系统设置为了其他类型的shell。
并不是必须要一直使用默认的交互shell .可以使用发行版中所有的可以的shell,只需要输入对应的文件名。eg:
/bin/csh //就进入了该shell 可以输入exit来退出。
2. shell的父子关系
用于登录某个虚拟控制器终端或在GUI中运行终端仿真器时所启动的默认的交互shell,是一个父级shell。
在CLI提示符后面输入/bin/bash命令或在其他等效的bash命令时,会创建一个新的shell程序。这个shell程序被称为子shell。子shell也拥有CLI提示符,同样会等待命令输入。
我们在输入一个bash之后一个子shell就出现了。我们可以通过PPID 进程ID来判断父子关系。
对应bash命令的参数 我们可以通过man bash查看。bash --help命令也可以提供额外的帮助。
我们可以通过exit来退出shell。
就算是不使用bash命令或者运行shell脚本 也可生成子shell。一种方法就算使用进程列表。
3. 进程列表
你可以在一行中指定要依次运行的一系列命令。你可以通过命令列表来实现,只需要在命令之间加入(;)即可。
pwd ; ls ; cd /etc
上面的例子,所有的命令按照顺序执行,不存在任何问题。不过哟这并不是进程列表。要成为进程列表,这些命令需要包含在括号里面。
(pwd ; ls ; cd /etc)
二者执行结果看起来没什么不同,但起到的效果确是非同寻常。括号的加入使命令列表变成了进程列表。生成了一个子shell来执行对应的命令。
说明进程列表是一种命令分组。
还有一种命令分组是将命令放在花括号中,并在命令列表的尾部加上分号。语法为{command;}。使用这种方式进行分组,并不会创建出子shell。
要知道是否生成了子shell 可以借助一个环境变量$BASH_SUBSHELL。如果为0 表示没有子shell,为1表示有。
在shell脚本中,经常使用子shell来进行多进程处理,但是采用子shell的成本不菲,会明显拖慢处理速度。
子shell同样存在问题。它并非真正的多进程处理,因为终端控制着子shell的I/O。
4. 别出心裁的子shell用法
在交互式的shell CLI中,还有很多更富有成效的子shell用法。进程列表、协程和管道都利用了子shell。它们都可以有效地在交互式shell中使用。 在交互式shell中,一个高效的子shell用法就是使用后台模式。在讨论如何将后台模式与子shell搭配使用之前,你得先搞明白什么是后台模式。
4.1 后台模式
在后台模式中运行命令可以在处理命令的同时让出CLI,以供他用。演示后台模式的一个经典命令就是sleep 。 sleep 命令接受一个参数,该参数是你希望进程等待(睡眠)的秒数。这个命令在脚本中常用于引入一段时间的暂停。命令sleep 10 会将会话暂停10秒钟,然后返回shell CLI提示符。
sleep 10
要想将命令置入后台模式,可以在命令末尾加上字符& 。把sleep命令置入后台模式可以让我们利用ps 命令来小窥一番。
sleep 10&
ps -f
//也可以使用jobs命令来显示当前运行在后台模式中的所有用户的进程(作业)
jobs
5. 外部命令
有时候也被称为文件系统命令,是存在bash shell之外的的程序。它们并不是shell程序的一部分。外部命令通常位于/bin,/usr/bin,/sbin,/user/sbin中。
ps就是一个外部命令。你可以使用which和type命令找到它。
当外部命令执行时,会创建一个子进程。这种操作被称为衍生。当进程必须执行衍生操作时,它需要花费时间和精力来设置新子进程的环境,所以外包命令多少还是有代价的。
6. 内建命令
内建与外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译称为一体。和外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译成了一体,作为shell工具的组成部分存在。不需要借助外部程序文件来运行。
cd 和 exit 命令都内建于bash shell。可以利用type 命令来了解某个命令是否是内建的。
type exit // type is a shell builtin
type cd //cd is a shell builtin
因为既不需要通过衍生出子进程来执行,也不需要打开程序文件,内建命令的执行速度要更快,效率也更高。。 要注意,有些命令有多种实现。例如echo 和 pwd 既有内建命令也有外部命令。两种实现略有不同。要查看命令的不同实现,使用type 命令的-a 选项。
type -a pwd
//pwd is a shell builtin
//pwd is /bin/pwd
type -a echo
//echo is a shell builtin
//echo is /bin/echo
6.1 使用history命令
一个有用的内建命令是history 命令。bash shell会跟踪你用过的命令。你可以唤回这些命令并重新使用。
要查看最近用过的命令列表,可以输入不带选项的history 命令。
history //一般会保留1000条
你可以设置保存在bash历史记录中的命令数。要想实现这一点,你需要修改名为HISTSIZE 的环境变量。
你可以唤回并重用历史列表中最近的命令。这样能够节省时间和击键量。 输入!! ,然后按回车键就能够唤出刚刚用过的那条命令来使用。
当输入!! 时,bash首先会显示出从shell的历史记录中唤回的命令。然后执行该命令。
命令历史记录被保存在隐藏文件.bash_history中,它位于用户的主目录中。
cat .bash_history
这里要注意的是,bash命令的历史记录是先存放在内存中,当shell退出时才被写入到历史文件中。
还有一些强制往历史文件里面插入记录 history -a
强制重新读取历史文件histoty -n
(mac终端执行无效,对个会话终端打开的时候,使用该命令,强制重新读取文件中的记录)
!10349
唤回历史记录中的命令,只需要感叹号加上历史列表中的编号即可。
6.2 命名别名
alias 命令是另一个shell的内建命令。命令别名允许你为常用的命令(及其参数)创建另一个名称,从而将输入量减少到最低。 你所使用的Linux发行版很有可能已经为你设置好了一些常用命令的别名。要查看当前可用的别名,使用alias
alias
-='cd -'
...=../..
....=../../..
.....=../../../..
......=../../../../..
1='cd -'
2='cd -2'
3='cd -3'
4='cd -4'
5='cd -5'
6='cd -6'
7='cd -7'
8='cd -8'
9='cd -9'
_=sudo
afind='ack -il'
d='dirs -v | head -10'
g=git
ga='git add'
gaa='git add --all'
gapa='git add --patch'
gb='git branch'
gba='git branch -a'
gbd='git branch -d'
gbda='git branch --no-color --merged | command grep -vE "^(\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d'
gbl='git blame -b -w'
gbnm='git branch --no-merged'
gbr='git branch --remote'
gbs='git bisect'
gbsb='git bisect bad'
gbsg='git bisect good'
gbsr='git bisect reset'
gbss='git bisect start'
gc='git commit -v'
'gc!'='git commit -v --amend'
gca='git commit -v -a'
'gca!'='git commit -v -a --amend'
gcam='git commit -a -m'
'gcan!'='git commit -v -a --no-edit --amend'
'gcans!'='git commit -v -a -s --no-edit --amend'
gcb='git checkout -b'
gcd='git checkout develop'
gcf='git config --list'
gcl='git clone --recursive'
gclean='git clean -fd'
gcm='git checkout master'
gcmsg='git commit -m'
'gcn!'='git commit -v --no-edit --amend'
gco='git checkout'
gcount='git shortlog -sn'
gcp='git cherry-pick'
gcpa='git cherry-pick --abort'
gcpc='git cherry-pick --continue'
gcs='git commit -S'
gd='git diff'
gdca='git diff --cached'
gdct='git describe --tags `git rev-list --tags --max-count=1`'
gdt='git diff-tree --no-commit-id --name-only -r'
gdw='git diff --word-diff'
gf='git fetch'
gfa='git fetch --all --prune'
gfo='git fetch origin'
gg='git gui citool'
gga='git gui citool --amend'
ggpull='git pull origin $(git_current_branch)'
ggpur=ggu
ggpush='git push origin $(git_current_branch)'
ggsup='git branch --set-upstream-to=origin/$(git_current_branch)'
ghh='git help'
gignore='git update-index --assume-unchanged'
gignored='git ls-files -v | grep "^[[:lower:]]"'
git-svn-dcommit-push='git svn dcommit && git push github master:svntrunk'
gk='\gitk --all --branches'
gke='\gitk --all $(git log -g --pretty=%h)'
gl='git pull'
glg='git log --stat'
glgg='git log --graph'
glgga='git log --graph --decorate --all'
glgm='git log --graph --max-count=10'
glgp='git log --stat -p'
glo='git log --oneline --decorate'
globurl='noglob urlglobber '
glog='git log --oneline --decorate --graph'
gloga='git log --oneline --decorate --graph --all'
glol='git log --graph --pretty='\''%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'\'' --abbrev-commit'
glola='git log --graph --pretty='\''%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'\'' --abbrev-commit --all'
glp=_git_log_prettily
glum='git pull upstream master'
gm='git merge'
gmom='git merge origin/master'
gmt='git mergetool --no-prompt'
gmtvim='git mergetool --no-prompt --tool=vimdiff'
gmum='git merge upstream/master'
gp='git push'
gpd='git push --dry-run'
gpoat='git push origin --all && git push origin --tags'
gpristine='git reset --hard && git clean -dfx'
gpsup='git push --set-upstream origin $(git_current_branch)'
gpu='git push upstream'
gpv='git push -v'
gr='git remote'
gra='git remote add'
grb='git rebase'
grba='git rebase --abort'
grbc='git rebase --continue'
grbi='git rebase -i'
grbm='git rebase master'
grbs='git rebase --skip'
grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}'
grh='git reset HEAD'
grhh='git reset HEAD --hard'
grmv='git remote rename'
grrm='git remote remove'
grset='git remote set-url'
grt='cd $(git rev-parse --show-toplevel || echo ".")'
gru='git reset --'
grup='git remote update'
grv='git remote -v'
gsb='git status -sb'
gsd='git svn dcommit'
gsi='git submodule init'
gsps='git show --pretty=short --show-signature'
gsr='git svn rebase'
gss='git status -s'
gst='git status'
gsta='git stash save'
gstaa='git stash apply'
gstc='git stash clear'
gstd='git stash drop'
gstl='git stash list'
gstp='git stash pop'
gsts='git stash show --text'
gsu='git submodule update'
gts='git tag -s'
gtv='git tag | sort -V'
gunignore='git update-index --no-assume-unchanged'
gunwip='git log -n 1 | grep -q -c "\-\-wip\-\-" && git reset HEAD~1'
gup='git pull --rebase'
gupv='git pull --rebase -v'
gwch='git whatchanged -p --abbrev-commit --pretty=medium'
gwip='git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit -m "--wip--"'
history='fc -l 1'
l='ls -lah'
la='ls -lAh'
ll='ls -lh'
ls='ls -G'
lsa='ls -lah'
md='mkdir -p'
please=sudo
po=popd
pu=pushd
rd=rmdir
run-help=man
which-command=whence
重命名
alias gs='git status'
在定义好别名之后,你随时都可以在shell中使用它,就算在shell脚本中也没问题。要注意,因为命令别名属于内部命令,一个别名仅在它所被定义的shell进程中才有效。这样相当于我只能在我当前打开的进程中使用,而不能在其他进程使用,很有局限性。
删除重命名
unalias gs
若要每次登入都能够使用这些命令别名,则可将相应的alias命令存放到bash的初始化文件/etc/bashrc中。