see also jlevy/the-art-of-command-line (multi-language)

Organised 2017 March.
Notes from 2012 April.
多年前(2012)写的“鸟哥的Linux 私房菜”学习笔记。 - chapters 10~13 -ing
基本的命令应该没什么变化吧。
原网站: 繁体 0160startlinux

Crtl + Alt + [F1]~[F6]
# 共有六个, tty1 ~ tty6 ,切换的方式为 Crtl + Alt + [F1]~[F6],其中, [F7] [F8] 为图形接口的使用。
man xxx => xxx(number) manual 中的命令级别 number 代表意义:

  • 1:一般用户可以使用的命令或可运行文件案;
  • 5:一些配置文件的文件内容格式;
  • 8:系统管理员能够使用的管理命令。

第十一章、认识与学习BASH

认识 BASH 这个 Shell

硬件、核心与 Shell

// Hardware <=> Kernel  <=> Shell,KDE,App

为何要学文字接口的 shell

// 通用,效率高

系统的合法 shell 与 /etc/shells 功能

/etc/shells #所有 shells
/etc/passwd // 每个用户的默认 shell

Bash shell 的功能

// history, tab, alias, job control, scripts, wildcard
// 当前登录的history在内存,注销时才会写入用户history文件。

Bash shell 的内建命令: type

type  [-tpa] commandName
[ ] :无参,说明式回显 commandName 类型,还和使用者语言有关
-t  :单词式回显:
      file    :外部命令
      alias   :别名
      builtin :bash内建
-p  :如果后面接的 commandName 为外部命令,会显示完整文件名;
-a  :会由 PATH 变量定义的路径中,将所有含 commandName 字符串的命令都列出来,包含 alias

命令的下达 "\"

//  \ 仅跳脱『紧接着的下一个字符』

Shell 的变量功能

什么是变量

变量的取用与配置 (echo, 变量配置守则, unset)

// 变量的取用: echo
    echo $varName
    echo ${varName}
    //  $varName 是 ${varName} 的简化。
// 变量配置守则
    // 赋值等号两边不能有空格
    // 不能以数字开头
    // 命令嵌套 eg : 
        ls -l `locate crontab` 
        // 反单引号 `命令` 或  $(命令) 等价 (推荐后者,因为容易识别,但是不要忘了那个$符号)。
unset varName #注意:此处没有'$'

环境变量的功能: env, export, declare

// env 与 export 可观察环境变量,其中 export 可以将自定义变量转成环境变量;
env #查看所有env变量
    HOME #cd ~
    SHELL
    HISTSIZE
    MAIL
    PATH
    LANG #语系
    RANDOM #/dev/random ; 0~32767 之间
        declare -i number=$RANDOM*10/32768 ; echo $number #0~9 之间
set #查看目前 bash 的所有变量 (环境变量+自定义变量);
    //  set 还可以配置命令环境,见4.4
    echo $$
        // PID
    echo $?
        // 前一个命令回传值
    cd - 或 cd $OLDPWD 
        // 前一个工作目录
    PS1='[\[email protected]\h#\#]\W\$'
        // 字符界面行首提示符
    \d :『星期 月 日』,如:"Mon Feb 2"
    \H :完整的主机名『www.vbird.tsai』
    \h :主机名在第一个小数点之前的,如『www』
    \t :24 小时HH:MM:SS
    \T :12 小时HH:MM:SS
    \A :24 小时HH:MM
    \@ :12 小时 am/pm
    \u :当前账号,如『root』;
    \v :BASH版本,如 3.2.25(1),仅取『3.2』显示
    \w :完整pwd (除了~之外);
    \W :当前所在文件夹 #利用 basename 函数取得工作目录名称
    \# :命令的顺序编号
    \$ :root 为 '#' ,否则 '$' 
    OSTYPE #linux-gnu
    HOSTTYP #i686 #硬件 类型
    MACHTYPE #i686-pc-linux-gnu #核心 类型
export varName #<=> declare -x varName 
    // 将 varName 设置为 env变量 类型 (原来可能只是个 自定义变量 )。
    // 对于子程序,  varName 在 本行之后才启动的子程序 中有效。(因为子程序会自动继承 自己被invoke时候 在父进程中有效的 env变量)。

影响显示结果的语系变量 (locale)

变量的有效范围

变量键盘读取、数组与宣告: read, array, declare / typeset

read -p "promt string" -t timeOutInSecond varName
    // 从键盘读取内容并存入varName; 
    -p  :prompt : 接提示字符
    -t  :timeout: 接等待的『秒数』
// array:
    var[1]='this is the first'
declare [-aixr] variable
    -a  :将 variable 定义为 数组 (array) 类型
    -i  :将 variable 定义为 整数 (int) 类型
    -x  :#<=> export, 将 variable 变成环境变量;
    -r  :配置成为 readonly 类型,且不能 unset
    declare -x varName #export varName 
        // 将 varName 设置为 env变量 类型 (原来可能只是个 自定义变量 )。
        // 如果export后没有varName,则 显示 所有 env变量。(-x 表示“转换为环境变量”参数,“-”不是chomd中的“去掉”)。
    declare +x varName
        // 将 varName 设置为 自定义变量 类型。(注意 +x 的含义,区别于 chmod)。
    declare -p varName
        // 显示 varName 变量类型(1.是 int(i) 还是 array(a) 还是 string( ); 2.是否 环境env变量(x); 3.是否只读(r))。

与文件系统及程序的限制关系: ulimit

ulimit
    //  ??? 看不懂,学完操作系统再看。http://is.gd/I6ccfR『限制用户的某些系统资源』的,包括可以开启的文件数量, 可以使用的 CPU 时间,可以使用的内存总量等等。
// hostname 更改主机名 how-to:三部曲
    hostname newHostname #执行此命令
    /etc/sysconfig/network #edit this file:
        NETWORKING=yes
        HOSTNAME=newHostname
    /etc/hosts #edit this file.

For ubuntu 18+:

hostnamectl set-hostname newHostname
hostname newHostname
echo 'newHostname' > /etc/hostname

# !!! cloud-init:
sed 's/preserve_hostname: false/preserve_hostname: true/' /etc/cloud/cloud.cfg
# or:
touch /etc/cloud/cloud-init.disabled

变量内容的 匹配删除,匹配替换,状况测试 http://is.gd/ScOCjc

**匹配删除**
    newVarName=${varName#/*xxx}
        'varName':源;'#':see below; 匹配并删除'/*xxx'(*:任意匹配)。
    '#' :头部对齐(源varName必须以'/'开头才能匹配到),最短匹配。
    '##':头部对齐,最长匹配。
    '%' :尾部对齐,最短匹配。
    '%%':尾部对齐,最长匹配。
**匹配替换**
    newVarName=${varName/oldString/newString}
        // 匹配 首次出现
    newVarName=${varName//oldString/newString}
        // 匹配 所有
**状况测试**
    // 1)给 新变量 赋默认值
        newVar=${oldVar<被匹配状况>defaultContent}
        // 若匹配成功,以 defaultContent 赋值 newVar;          否则 以 oldVar 赋值 newVar。<被匹配状况>可能为:
     - oldVar 未配置
    :- oldVar 未配置 或 为空字符串
     + oldVar 已配置
    :+ oldVar 已配置 且 非空字符串
    // 2)给 双变量 赋默认值
        newVar=${oldVar<被匹配状况>defaultContent}
        // 若匹配成功,以 defaultContent 赋值 newVar 和 oldVar;否则 以 oldVar 赋值 newVar。<被匹配状况>可能为:
     = oldVar 未配置
    := oldVar 未配置 或 为空字符串
        // 记忆:将'='理解为赋值号。
    // 3)输出错误提示
        newVar=${oldVar<被匹配状况>errInfo}
        // 若匹配成功,输出 oldVar和errInfo 到 stderr;         否则 以 oldVar 赋值 newVar。<被匹配状况>可能为:
     ? oldVar 未配置
    :? oldVar 未配置 或 为空字符串

别名 和 历史

命令别名配置: alias, unalias

alias lm='ls -alF --color=always|more'
unalias lm

历史命令:history

history [n]|[-c]|[-r|w|a [fileName]]
[]:无参数, 显示所有在 当前shell的 hist内存 的hist。
n:数字,最近n个(包括最后一个)。
-c:clear all 当前shell的 hist内存。
-r:从fileName覆盖读入到 当前shell的 hist内存。(默认文件为 ~/.bash_history)
-w:当前shell的 hist内存 覆盖写入到 fileName。(默认文件为 ~/.bash_history)
-a:当前shell的 hist内存  追加   到 fileName。(默认文件为 ~/.bash_history)
echo $HISTSIZE
    // hist size
!number; !command; !!
// 命令一般是先被记录才被执行,这三句话有额外的转换,然后再记录,再执行。所以 !num 中的num是正确的,不需要+1,是用 history 命令看到的那个编号。
// logout时,可用 -c + -w 清除历史,防止cracker,尤其对root的crack。

Bash Shell 的操作环境

命令搜索优先级:

// 直接命令(相对或绝对路径);再 alias;再 shell-buildin;再由 $PATH 找到。
alias echo='echo -n'; type -a echo

Bash 的进站与欢迎信息: /etc/issue, /etc/issue.net, /etc/motd

Bash 的环境配置文件 #http://minus.com/mbbIx0hdzh

// 系统整体配置
    /etc/profile : (for login shell) 系统整体配置,不要更改. 它会引入:
        /etc/inputrc : 与bash输入相关的设置,例如热键,输入提示音。
        /etc/profile.d/*.sh : bash颜色,命令别名等。
        (/etc/profile.d/lang.sh :引入:
    /etc/sysconfig/i18n : 语言等)
// 个人配置 #~/.bash_login 或 ~/.bash_profile 或 ~/.profile #按此顺序优先级只读取一个
    ~/.bash_profile :{{{ **引入 ~/.bashrc ** + $PATH }}}。它会引入:
        ~/.bashrc (for non-login shell. **not-login只会读这一个文件。**): 引入:
    /etc/bashrc (ubuntu为bash.bashrc) :{{{ umask + $PS1 }}} 引入:
        /etc/profile.d/*.sh :(同 系统整体配置 )。
        // umask:目前使用者在创建文件或目录时候 默认减掉的 权限值。
            // 创建文件时:(-rw-rw-rw-) - (-----w--w-) ==> -rw-r--r-- :『文件』默认 没有『可运行( x )权限』。
            // 创建目录时:(drwxrwxrwx) - (d----w--w-) ==> drwxr-xr-x :x 与是否可以进入此目录有关。
// 其他配置:
    /etc/man.config (centOs) 或 /etc/manpath.config (suse,ubuntu) ;{{{ $MANPATH }}} 即到哪个路径去找手册。
    ~/.bash_history :每次登陆 bash 后,bash 会先读取这个文件,将所有的历史命令读入内存。
    ~/.bash_logout : 注销 bash 前的命令,例如,清屏、清缓存、备份文件。
source ~/.bashrc
    // source 或小数点(.)命令 可以使某些配置不用重新登录就生效

终端机的环境配置: stty, set

stty [-a] #终端机的环境配置 (Setup TeleTYpe terminal unit).
    eof     = ^D : !!! end of file ,『结束输入流(例如重定向时)』;没有输入流时结束当前shell-session。
    stop    = ^S : !!! 停止 目前屏幕的输出;
    start   = ^Q : !!! 重启 目前屏幕的输出;
    erase   = ^? : 向后删除字符,
    intr    = ^C : interrupt (中断) 的讯号给目前正在 run 的程序;
    kill    = ^U : 删除在目前命令列上 光标左边 的所有文字;
    werase  = ^W :删除在目前命令列上 光标左边 的所有文字;
    quit    = ^\ : 送出一个 quit 的讯号给目前正在 run 的程序;?????????
    susp    = ^Z : suspend 送出一个 terminal stop 的讯号给正在 run 的程序。
    rprnt   = ^R :搜索以往命令,(非开头匹配,是任意位置匹配)。
    // eg: 
        stty erase ^h #那么从此之后,你的删除字符就得要使用 [ctrl]+h 啰,按下 [backspace] 则会出现 ^? 字样
set [-uvCHhmBx] #一般用不到???

通配符与特殊符号

cp -a /etc/[^a-z]* /tmp
    // 找出 /etc/ 底下,档名开头非为小写字母的文件名 复制到 /tmp 中。
    -a: same as -dR --preserve=all.
// 特殊符号:你的『档名』尽量不要使用到的这些字符啦!
    //  批注符号
    ;   连续命令下达分隔符
    !   逻辑『非』
    ~   用户的家目录
    &   工作控制 (job control):将命令变成背景下工作
    !   逻辑运算意义上的『非』 not 的意思!
    ` ` 两个『 ` 』中间为可以先运行的命令,亦可使用 $( )。(!!!  $ 符号不要丢了 !!!)
    ( ) 在中间为子 shell 的起始与结束

数据流 和 程序 的方向控制

数据流重定向 >, >>, <, <<, tee, /dev/null

perl phase1_server.pl >output.txt 2>&1 & #bg job
    //  2是stderr, 所以这个 2> 是很容易理解的. 
    //  默认是1(stdout), 可以省略
    // 注意:数字后没空格
    // 不同流重定向到同一文件流:
        find /home -name .bashrc > list.txt 2> list  <==错误(由于两股数据同时写入一个文件,又没有使用特殊的语法, 此时两股数据可能会交叉写入该文件内,造成次序的错乱。)
        find /home -name .bashrc > list.txt 2>&1     <==正确//复合重定向 (推荐); 或者
        find /home -name .bashrc &> list         <==正确
cat > catfile < ~/.bashrc
    // 简单输入重定向。(范例七:用 stdin 取代键盘的输入以创建新文件的简单流程。)
cat > catfile << "xxx"
    > This is a test.
    > OK now stop
    > xxx  <==输入这关键词,立刻就结束而不需要输入 [ctrl]+d 。
tee [-a] filename #复制重定向,不影响原有输出
    -a :append:追加写入。默认是覆盖写入。
/dev/null
    // 黑洞dev 
    // eg: 
        find /home -name .bashrc 2> /dev/null
重定向用于:
    // 屏幕输出的信息很重要,而且我们需要将他存下来的时候;
    // 背景bg运行中的程序,不希望他干扰屏幕正常的输出结果时;
    // 一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的运行结果,希望他可以存下来时;
    // 一些运行命令的可能已知错误信息时,想以『 2> /dev/null 』将他丢掉时;
    // 错误信息与正确信息需要分别输出时。

简单的流程控制, 命令运行的判断依据.

cmd_1 && cmd_2; cmd_3 || cmd_4
    // 判断原则: 0 ,非0 。
    // && : 如果 cmd_1 返回正确( 0),则 执行 cmd_2。
    // && : 如果 cmd_3 返回错误(~0),则 执行 cmd_4。
// eg: ls tmp 2> /dev/null || mkdir tmp -p && touch tmp/testFile
    // Linux 中 || 与 && 相同优先级. pipe(|) 优先级低于 ||和&&.
    // 但是要避免自己的逻辑错误 eg:
    ls /tmp/vbirding && echo "  exist " || echo "no exist" <==正确
    ls /tmp/vbirding || echo "no exist" && echo " exist  " <==错误(两个echo都会起作用)
    // 结论:“command2 后”,先 “&& command2” 后 “|| command3” 。(原因是:其中的 command2 与 command3 一般放置着 肯定可以运行成功的命令。)

管道 | (pipe)

// 注意1:pipe 的第二个命令 可以直接处理 第一个命令的 std out,不能直接处理 std err。
// 注意2:并不是所有命令都会接受std in,不接受std in的,放在第二个命令处也不会接受std in,例如 ls,cp,mv 等。

撷取: cut, grep

grep [-acinv] [--color=auto] 'key string' pendingFilename
    -v :inVerse:反向,即:输出没有 'key string' 的那些行
    -a :text Available:将 binary 文件以 text 文件 对待
    -c :Count only:仅 输出数量
    -i :case Insensitive:忽略大小写
    -n :line Number:同时输出行号
cut -[dfc] #eg: echo $PATH | cut -d ':' -f 3;  export | cut -c 12- ; export | cut -c 12-20 ; export | cut -c  -15;
    // 常用格式:
        cut -d'delimiterSign' -f fieldNumList   <==用于有特定分隔字符 否则:
        cut -c 字符区间         <==用于排列整齐的信息
    // 参数含义:
        -d  :Delimiter :后面接分隔字符(只能是单个字符,且不支持转义字符),一般与 -f 一起使用。 -f  :Fiels:依据 -d 的结果,用 -f 取出第 fieldsNumber 段(从1开始数)。
        -c  :Character:以Character(字符)计量 (从1开始数),而不是fields。
    // 注:last | cut -d ' ' -f 1,3; 是错误的,因为 root 与  pts/1 之间空格有好几个。可以使用:
        last | cut -c 1-4,22-38

排序与统计: sort, uniq, wc

// 注意:使用类似功能前,建议用 LANG=C 规范语境
sort [-fbMnrtuk] [file or stdin];  #eg: 
    cat /etc/passwd | sort -nrt ':' -k 3|less
        -f  :Fold lower to upper: 忽略大小写
        -b  :ignore leading Blanks
        -M  :Month: 以月份名(例如 JAN, DEC 等)排序
        -n  :Numeric:以『纯数字』模式排序,而非默认的文本模式 #文本序(0,10,11,1,12)vs 数字序(1,1,10,11,12)
        -h  :Human numeric :例如 1K,2G
        -r  :Reverse:逆序
        -u  :Unique,相同的数据,仅输出首次;(与-c (Check) 同用时,有其他意思)
        -t  :Tab:分隔符(默认 [tab] )
        -k  :Key positions: (相当于 grep的 -f) :以给定 fieldNumberList 内容排序
uniq #eg: 
    last | cut -d ' ' -f1 | sort | uniq -c
        // uniq 只对连续重复起作用,所以 先 uniq 再 sort 会有另一种输出
        -i  :case Insensitive:忽略大小写
        -c  :Count:同时计数
wc [-lwmc] [file or stdin];  #eg: 
    last | grep [a-zA-Z] | grep -v 'wtmp' | wc -l 
        // (last 会输出空白行与 wtmp 字样在最底下两行,利用grep去除).
        // 默认会输出 行数 + 词数 + 字符数
        -l  :Line
        -w  :Word
        -m  :character
        -c  :byte

双向重导向: tee (见 ch5)

字符转换: tr, col, join, paste, expand

tr [-ds] SET1 [SET2]; #translate; 常配合正则表达式使用
    tr -d SET1     : 删除 符合 SET1 匹配的字符(串)eg: cat /etc/passwd | tr -d ':'
        // eg:cat file.dos | tr -d '\r' > file.unix (\r 表示 ^M (tr不接受直接用'^M'),即dos比unix换行多出来的 http://ss64.com/bash/tr.html)
    tr    SET1 SET2: 替换 符合 SET1 匹配的字符(串)
col [-xb]
    -x  :[tab] 转 对等 空格
    -b  :在文字内有反斜杠 (/) 时,仅保留反斜杠最后接的那个字符 ??????????
join -t ':' -1 4 /etc/passwd -2 3 /etc/group
    // 比较 -1 4(第1个文件第4栏) 和 -2 3 (第2个文件第3栏),将 结果相同的 合并行,剪切第2个文件 第3栏和多余的分隔符 到 合并行 行首,(如果与 第一个文件 行首相同,即 -1 1,则只保留一个,不重复)。(可以与 head -n 3 /etc/passwd /etc/group 对比。)
    // 注意:join前必须先sort。
    -t  :separaTor:默认空格
    -i  :忽略大小写
    -1  :数字 1 ,代表 第1个文件
    -2  :数字 2 ,代表 第2个文件
    // eg:
        join -t ':' /etc/passwd /etc/shadow ; (可以与 head -n 3 /etc/passwd /etc/shadow 对比。)
paste /etc/passwd /etc/shadow
    // 直接将行号相同的贴在一起,且中间以 [tab] 键隔开
    -d  :Delimiter :默认  [tab]  
    -   :如果 file 部分写成 - ,表示 standard input
expand [-t NUM] file
    -t NUM:-t 后面一般接数字。tab 按键可以用 8 个空格键取代。
    // eg:
        grep '^MANPATH' /etc/man.config | head -n 3 | expand -t 6 - | cat -A 
        // 将 /etc/man.config 内行首为 MANPATH 的字样就取出, 三行; 6 个字符来代表一个 [tab] 的长度

流分割: split

split -b 300k originalFile splitedFilePrefix #文件分割
    -b  : Bytes : 可加单位,例如 b, k, m 等;
    -l  : Lines : 以行数分割。
        // eg: ls -al / | split -l 10 - lsroot;  wc -l lsroot*; (参考下面对于 减号 的解释)
    splitedFilePrefix : 新文件名为:"splitedFilePrefix" + "aa/ab/ac...."
    // 合并:
        cat splitedFilePrefix* >> originalFile

参数代换 xargs

xargs [-0epn] command #对于不支持 pipe 的 command ,可以透过 xargs 来提供 pipe 支持。
    -0  :如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等等字符时,这个 -0 参数 可以将他还原成一般字符。这个参数可以用于特殊状态喔!
    -e  :End before key-word. 读取参数过程中,遇到某词就结束。
        // 注意:
        // 1. 必须是一个整词(以空格判断),不能只用开头或结尾,不能含空格。
        // 2. 其后不能有空格,key-word 可以不用“”引起来。
    -p  :promt : 执行 command 之前,输出命令行,并要求确认执行
    -n NUM :max-args : 每次运行 command 最多给 NUM 个 args ,多次执行直到用完 args 。
    command: 默认 command 是 echo 
    // eg:
        cut -d':' -f1 /etc/passwd  | head -n 15 | xargs -p -n 3 -e'lp' finger
    // 取 /etc/passwd 第一列; 只取前三行; 每3个args(words)一组给 finger 执行,遇到‘lp’就停下,执行前确认。(finger: 显示某(几)个账户信息)

- (减号)

// 如果命令 某参数 应为 文件名,但却是 "-", “-” 就会被当成 stdin/stdout,in/out取决于原参数是输入还是输出。
// 经典实例(没有实用价值,但是很经典):
    tar -cvf - /home | tar -xvf - #将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是传送到 stdout; 经过pipe将 tar -cvf - /home 传送给后面的 tar -xvf - 。后面的这个 ‘-’ 取用前一个命令的 stdout, 因此,我们就不需要使用 file 了!

== 第十一章 end ==

第十二章、正规表示法与文件格式化处理

文件的格式化与相关处理

printf: 格式化列印

awk:好用的数据处理工具

文件比对工具:, diff, cmp, patch

diff
diff [-bBi] oldFile|oldDir newFile|newDir
    -b: 忽略重复空格
    -B:忽略重复空行
    -i:case-Insensitive
    eg1:比对 passwd.old 与 passwd.new 的差异:
        [[email protected] test]# diff passwd.old passwd.new
        4d3    <==左边第四行被删除 (d) 掉了,基准是右边的第三行
        < adm:x:3:4:adm:/var/adm:/sbin/nologin  <==这边列出左边(<)文件被删除的那一行内容
        6c5    <==左边文件的第六行被取代 (c) 成右边文件的第五行
        < sync:x:5:0:sync:/sbin:/bin/sync  <==左边(<)文件第六行内容
        ---
        > no six line                    <==右边(>)文件第五行内容
    eg2: diff /etc/rc3.d/ /etc/rc5.d/
cmp 
patch 
patch [-R] [-pN] < fileName.patch
    -p  :『取消几层目录』的意思。
    -R  :Reverse, 还原
    eg1: patch -p0 < passwd.patch #update
        -rw-r--r-- 1 root root 1929 Feb 10 14:29 passwd.new
        -rw-r--r-- 1 root root 1929 Feb 10 15:12 passwd.old
    eg2: patch -R -p0 < passwd.patch #reverse
        -rw-r--r-- 1 root root 1929 Feb 10 14:29 passwd.new
        -rw-r--r-- 1 root root 1986 Feb 10 15:18 passwd.old
    范例一:以 /tmp/test 内的 passwd.old 与 passwd.new  制作补丁文件
        [[email protected] test]# diff -Naur passwd.old passwd.new > passwd.patch
        [[email protected] test]# cat passwd.patch
--- passwd.old  2009-02-10 14:29:09.000000000 +0800 <==新旧文件的资讯
+++ passwd.new  2009-02-10 14:29:18.000000000 +0800
@@ -1,9 +1,8 @@   <==新旧文件要修改数据的界定范围,旧档在 1-9 行,新档在 1-8 行
 root:x:0:0:root:/root:/bin/bash
 bin:x:1:1:bin:/bin:/sbin/nologin
 daemon:x:2:2:daemon:/sbin:/sbin/nologin
-adm:x:3:4:adm:/var/adm:/sbin/nologin      <==左侧文件删除
 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
-sync:x:5:0:sync:/sbin:/bin/sync           <==左侧文件删除
+no six line                               <==右侧新档加入
 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
 halt:x:7:0:halt:/sbin:/sbin/halt
 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

文件列印准备工具: pr

== end ==

第十三章、学习 Shell Scripts

什么是 Shell Script

干嘛学习 shell scripts:

// 自动化管理host状态; 管理系统服务; 简单入侵侦测; batch批处理; 配合awk和正则表达式简单处理数据/文本; 无需其他支持 跨unix-like平台.

第一个 script 的撰写与运行

// 如果使用 sh scriptFilename.sh运行, script 不需要具备运行 x ,只要能读 r 就可以.
// 首行 #!/bin/bash : 执行此脚本的 shell
// 接下来是 : 功能 版本 作者 版权 历史 特殊命令的绝对路径 环境变量(PATH,LANG)
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin; export PATH; LANG=C #如果有输出, 最好先 LANG=C 
exit 0
// 如果scrip 由 exit 退出, echo $? 会给出 script 的 exit 的数值.

撰写 shell script 的良好习惯创建

// [tab], vim

简单的 shell script 练习

简单范例: 对谈式脚本, 随日期变化, 数值运算

read -p "Please input your filename: " -t 4 fileuser; filename=${fileuser:-"filename"} #read 不支持 "\n".
date1=$(date -d'2 days ago'  +%Y.%m.%d-%H:%M)
    date [选项]... [+格式]
    -d'字符串', --date='字符串'   如果不是现在,可用字符串指定具体日期/时间('20090101'),也可用描述性语句('2 days ago'). -d可跟空格, --date不可以.
    %F    完整日期格式,等价于 %Y-%m-%d
    %H    小时(00-23)
    %l    时(1-12)
    %m    月份(01-12)
    %M    分(00-59)
    %n    换行
    %R    24 小时时间的时和分,等价于 %H:%M
    %s    自UTC 时间 1970-01-01 00:00:00 以来所经过的秒数
    %S    秒(00-60)
    %t    输出制表符 Tab
    %T    时间,等于%H:%M:%S
    %y    年份最后两位数位 (00-99)
    %Y    年份
echo -e "\n now is : $date1. \n"
    -e : Enable interpretation of backslash escapes #default is -E: disable
    -n : 不输出新行
total=$(($firstnu*$secnu)) #或者:  declare -i total=$firstnu*$secnu #推荐双括号式; 可用的计算有 +, -, *, /, % .

script 的运行方式差异 (source, sh script, ./script)

sh filename / 直接命令(绝对路径/相对路径/$PATH内) 
    // 会启用一个新的 bash 子程序, 子程序内的 变量 或动作 在执行完后失效
source filename
    // 直接执行, 所以 被引用脚本执行完后 变量仍然持续有效

善用判断式

test 命令

// 1.文档属性 (如 存在否)
    -e 存在  (常用)
    -f 存在、File(常用)
    -d 存在、Directory(常用)
    -b 存在、Block     device 装置
    -c 存在、Character device 装置
    -s 存在、Size > 0
    -S 存在、'S'ocket 
    -p 存在、Pipe (FIFO)  
    -L 存在、'L'ink # -l : Long int ??? 
// 2.文档权限 (如 可读否) (root 权限常有例外, 可能与ls显示不同)
    -r 存在、可读
    -w 存在、可写
    -x 存在、可执行
    -u 存在、setUid(4000): 仅文件.任何用户 执行该文件时, 权限 等同于 文件所有者.#eg: /usr/bin/passwd : -rwSr-xr-x. (实际全小写)
        chmod u+s filename #加上setuid标志
    -g 存在、setGid(2000): 仅目录.任何用户 创建的文件都 与该目录 属于相同的组.
        chmod g+s dirname
    -k 存在、sticKy(1000): 文件/目录. 仅所有者/root可删除. #eg: /tmp/ : drwxrwxrwT(实际全小写)
        chmod o+t filename
// 3.文档比较 eg: test file1 -nt file2
    -nt Newer Than  
    -ot Older Than
    -ef samE File #have the same device and inode numbers. 可用于判断 hard link
// 4.整数比较 eg:  test n1 -eq n2
    -eq EQual
    -ne Not Equal
    -gt Greater Than
    -lt Less Than
    -ge Greater than or Equal
    -le Less than or Equal
// 5.字符串 (必须使用引号) 
    [-n]    string      length Not zero. 非空为true. # -n 可省略
    -z      string      length Zero
    str1 == str2        相等为true # ==(推荐) 和 = 作用都是比较
    str1 != str2        不等为true
// 6. 多重条件判定,eg: test -r filename -a -x filename
    -a  And
        test -r file -a -x file #file 同时有 r 与 x 权限时 true
    -o  Or
    !   非
        test ! -x file 

利用判断符号(中括号) [ ]

[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" 
// 注意: 
    // a. ==(推荐) 和 = 作用都是比较; 
    // b. 中括号内各个组件之间 必须使用空格; 
    // c. 必须使用引号; 
    // d. 上式等同于:
        [ "$yn" == "Y" ]||[ "$yn" == "y" ] && echo "OK, continue" #!!! 中括号里只能有一个判别式, [ "$yn" == "Y" || "$yn" == "y" ]是错误的
        中括号之间的 &&, || 与命令下达的状态 (echo之前的) 的作用不同了.

Shell script 的默认变量($0, $1...): shift

$0 : 启动此脚本时的 无参命令行路径 (1.不一定是什么路径; 2.即使用 "bash xxx" 启动, $0也不包括shell名; 3.不含参数)
$# : No. : 参数个数/最后一个参数脚标
[email protected] : Array: 所有参数
$* : 字符串, 所有参数. #一般用不着这个东西
shift NUM #所有变量向左偏移, 溢出的丢掉 eg: 
    ./tmp 1 2 3 4 5 6
    echo "Total parameter number is ==> $#"
    echo "parameters ==> '[email protected]'"
    shift 
    echo "Total parameter number is ==> $#"
    echo "parameters ==> '[email protected]'"
    shift 3 
    echo "Total parameter number is ==> $#"
    echo "parameters ==> '[email protected]'"
    // 会输出:
    Total parameter number is ==> 6
    parameters ==> '1 2 3 4 5 6'
    Total parameter number is ==> 5
    parameters ==> '2 3 4 5 6'
    Total parameter number is ==> 2
    parameters ==> '5 6'

条件判断式

利用 if .... then: 单层简单条件, 多重复杂条件, 检验$1内容, 网络状态, 退伍

    if#eg1:
        testing=$(netstat -tuln | grep ":80 ")   # 侦测看 port 80 在否?
        if [ "$testing" != "" ]; then
            echo "WWW is running in your system."
        fi
    if#eg2:
        read -p "Please input your demobilization date (YYYYMMDD ex>20090401): " date2
        date_d=$(echo $date2 |grep '[0-9]\{8\}')   # 看看是否有八个数字
        if [ "$date_d" == "" ]; then
            echo "You input the wrong date format...."
            exit 1
        fi
    if_else#eg:
        if [ ]; then
        else
        fi
    elif#eg:
        if [ ]; then
        elif [ ]; then
        else
fi

利用 case ..... esac 判断

case $var in    <==关键字为 case ,还有变量前有钱字号
  "第一匹配")       <==每个变量内容建议用双引号括起来,关键字则为小括号 )
    程序段
    ;;          <==每个类别结尾使用两个连续的分号来处理!
  "第二匹配")
    程序段
    ;;
  *)            <== 相当于 wildcard 
    其他程序段
    exit 1
    ;;
esac

function

function printit(){
    echo "Your choice is $1"    #  这个 $1 是调用此函数的参数, 不是父程序的参数. 调用方法和执行普通程序一样.
}
printit 2       #  $1 为 2

loop (循环/回圈)

不定循环

while...do...done #eg:
    while [ condition ]
    do
        xxxx
    done
until...do...done #eg:
    until [ condition ] 
    do
        xxxx
    done

固定循环

for_in...do...done #eg1:
    users=$(cut -d ':' -f1 /etc/passwd)  # 撷取帐号名称
    for username in $users               # 开始回圈进行!
    do
        id $username
        finger $username
    done
$(seq )#eg2:
    network="192.168.1"              # 先定义一个网域的前面部分!
    for sitenu in $(seq 1 100)       # seq 为 sequence(连续) 的缩写之意
    do
        ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
        if [ "$result" == 0 ]; then
        echo "Server ${network}.${sitenu} is UP."
        else
        echo "Server ${network}.${sitenu} is DOWN."
        fi
    done

for...do...done

for (( i=1; i<=$nu; i=i+1 ))
do
    xxxx
done

追踪与 debug

sh [-nvx] scripts.sh
    -n  :Non-run. 不运行,输出语法问题; 没问题 就 没输出.
    -v  :Verbose. 输出所有行 再运行.
    -x  :eXcute.  输出用到的行(行前增加'+'号), 新行输出对应结果.

== 第十三章 end ==