2017-04-07 03:24:28 +0000   |     linux shell   |   Viewed times   |    

复习Linux文件系统的基本概念。方便以后查阅。这不是命令大全,只强调几个我自己需要搞清楚的重点。

主要参考下面三本书:

  1. 《The Linux Command Line》(俗称TLCL):主要了解Shell命令行。
  2. 《Linux程序设计-第4版》:配合下面的《Linux内核设计》,有知识点不清楚的情况,当工具书查阅。
  3. 《Linux内核设计-第3版》:同上

另外网上还有一些不错的简明教程,推荐下面两个,直接了当,不繁文缛节。 极客学院Shell教程 IBM-学习Linux命令行

另外learnshell.org网站,可以边学边动手做练习。Leetcode的Shell部分只有4题,之能拿learnshell.org练手。

路径

绝对路径

/表示根目录。

~表示当前用户HOME目录。

下面的绝对路径表示: 更换当前工作目录至根目录下的usr文件夹下的bin文件夹。

[me@linuxbox bin]$ cd /usr/bin

pwd命令显示绝对路径:

[me@linuxbox bin]$ pwd
/usr/bin

相对路径

  1. .指当前工作目录。
  2. ..指当前工作目录的父目录。

如果当前工作目录为系统根目录下的usr文件夹,

[me@linuxbox usr]$ pwd
/usr
[me@linuxbox usr]$ cd ./bin

那么原先的cd /usr/bin也可以写成c ./bin

记住!几乎在所有的情况,./是可以省略的。因为./默认加入$PATH环境变量。

[me@linuxbox usr]$ cd bin

选项和参数

掌握了下面这个命令的通用格式,

command -options arguments

options一般有 长选项,用两个分隔符分割--,和 短选项,用一个分隔符-分割,之分。同一件事,经常同时有长选项和短选项两个版本,效果一样。

遇到不懂的就用-h查手册。 会查手册,基本Linux命令行就能用起来了。同样,-h是短选项,它还有个长选项的版本-help

help信息太多,一般结合lessgrep命令显示,看得更清楚,

wget -h | less   #更多内容,翻页往下看
wget -h | grep XXX  #只显示包含有XXX的内容

文件系统

记住这句话:

在类Unix系统中,一切皆是文件! (大多数都是文本文件,所以Linux中没有秘密存在!)

类UNIX系统中像open(),read(),write()这样的系统调用,直接面对的不是物理介质。而是虚拟文件系统(VFS)。它是一套通用抽象文件模型,拥有统一的接口和数据结构。不管底下的物理介质如何,系统调用面对的都是一套统一的VFS接口。

VFS有4种主要对象类型:superblock, dentry, inodefile.

  1. superblock: 文件系统的宏观控制信息。
  2. inode: 除了文件名之外的描述文件属性的元信息。
  3. dentry: 文件名和inode对象的映射。
  4. file: 文件本体。目录也是文件。

比如/etc/foo路径,有两个目录文件: 根目录/etc目录,以及一个普通文件foo组成。先通过dentry中的filename-inode映射,找到对应的inode编号,并找到inode。找到inode就能找到具体的文件所在的位置。

关于inode有一篇很好的文章:阮一峰的 《理解inode》

变量和符号的展开

$展开变量

Shell是弱类型语言,声明变量的时候,不需要定义变量类型。基本都是当成字符串处理。要使用的时候带上美元符$,解释器就会展开,

value=bitch
echo $value
bitch

!注意: 变量名和等号之间不能有空格。

关于变量名的规约如下,

分词,切割

Shell对输入单词的切割比较奇怪,比如像下面这个写法,解释器会默认将$1识别成输入的参数之一来展开。如果当前没有输入参数,则输出空缺。

echo total is $1.00
total is .00

字符串也一样,下面这样紧挨着,系统是不分词的,会以为变量名就是$aaaHello

aaa=Hello
echo $aaaRonald

想帮助解释器认清变量的边界,有两种方法。第一种,在变量外面加${},如下所示。推荐每个变量都用这种写法,避免歧义。 不要为了省一个花括号导致不必要的麻烦。

echo ${aaa}Ronald
HelloRonald

第二种,给字符串加上单引号或者双引号,

echo $aaa"Ronald"
HelloRonald
echo $aaa'Ronald'
HelloRonald

当然加个空格解释器也能能正确区分需要展开的变量和普通字符串的边界。但输出的两个单词之间也会加空格。这些细节看上去很简单,但实际上每种语言在处理这些细节都有自己的风格。

aaa=Hello
echo $aaa Ronald
Hello Ronald

展开函数结果

可以用$()把一个函数括起来,可以将函数输出作为一个变量使用。

echo $(ls)

也可以用两个倒引号把一个函数括起来,

echo `ls`

展开算数表达式

$(())括起来的内容表示一个数学计算。

echo $((2 + 2))
4

模式展开

{}可以批量替换某个模式。

echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back

路径展开

\~可以展开成当前用户的根目录,

echo ~
/Users/Wei

转义符

\: 转义符。(转义符的思想来自C语言)

双引号禁止绝大部分展开

"": 双引号禁止除了参数符$,反斜杠转义符\和倒引号之外的所有展开。

上面这条非常重要。没有读到正式定义之前,很多人都有这个困扰。

下面的例子展示了双引号管不住带$符的变量展开。

aaa=Hello
bbb=Hi
merged="$aaa Wei $bbb Wei"

当然也管不住加花括号的变量${}

aaa=Hello
echo "${aaa}"
Hello

而且双引号还管不住$(())这样的计算展开,

echo "$((3+3))"
6

基本是沾美元符$就管不住。

下面的例子展示了双引号管不住反斜杠\将后面的字符转意。

echo "\nRonald"

Ronald

至于管不住倒引号,也想不出什么例子来说明。

单引号禁止所有展开

'': 单引号是最强的引用等级,禁止所有展开。

下面是一个单引号和双引号的对比,双引号管不住$,${}变量,$(())计算,\转意。单引号能关注所有。

echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/me/ls-output.txt a b foo 4 me
echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt {a,b} foo 4 me
echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER

运行Shell脚本有两种方法

第一种,作为可执行程序

比如我有一个最基本输出Hello World!的脚本,保存为test.sh

#!/bin/bash
echo "Hello World !"

开头#!/bin/bash告诉系统用哪个解释器来执行脚本。

接下来,只需要给脚本添加执行权限,就可以当一个可执行程序执行了。因为类UNIX系统本质上 一切都是文件。因此可执行程序也可以看成一个普通的储存二进制流的文本文件。

chmod +x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本

如果将.当前工作路径添加到$PATH环境变量,可以省略./。而且扩展名.sh实际上并不影响脚本的执行,所以直接test加回车就可以了。

test
Hello World !

添加环境变量$PATH可以用export命令,

export PATH=$PATH:.:..

如果想永久扩充$PATH变量,需要把这行写入~/.profile文件。Mac系统下,对应的是~/.bash_profile文件。注意以上两个文件只对单个用户有效。

也可以用/etc/environment来设置环境变量,操作系统在登录时使用的第一个文件就是/etc/environment文件。但它不接受命令,只能直接为变量赋值,比如,

PATH="$JAVA_HOME:$PATH:..:."

如果要让环境变量设置立即生效,需要运行下面命令,

source /etc/environment

需要注意,Mac系统没有/etc/environment文件,此方法不适用。

另外,注意以上的方法对bash适用,但对zsh不适用。zsh的配置文件在~/.zshrc。同样是添加export命令,

export PATH=$PATH:.:..

第二种,作为解释器参数

这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

/bin/sh test.sh
/bin/php test.php

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

Shell支持数组

用下面的形式声明数组

array_name=(value1 value2 value3 value4 value5)

test_array=(aaa bbb ccc ddd eee)

也可以用下标直接赋值,而且不要求下标连续。

test_array[10]=iii

访问也是用下标来访问,

echo ${test_array[1]}
aaa

访问数组的元素,有讲究

想要遍历数组的所有元素,假设我的数组如下,

MY_ARRAY[0]="Hello"
MY_ARRAY[1]="World"
MY_ARRAY[2]="Hello World"

直接写数组变量是不行的,只输出首元素,

for STR in ${MY_ARRAY}; do  #错误!只会输出首元素 "Hello"
    echo $STR
done

# Output:
# Hello

要在下标的位置用[@],而且一定要在${}外面加上双引号,双引号有助于允许变量带空格。

The reason you double quote variables is because the contents of the variable may include spaces.

for STR in "${MY_ARRAY[@]}"; do #正确遍历数组的姿势!
    echo $STR
done

# Output:
# Hello
# World
# Hello World

${}外面不用双引号,所有带空格的字符都会被自动分割开,

for STR in ${MY_ARRAY[@]}; do #错误!这样字符串不能含有空格。
    echo $STR
done

# Output:
# Hello
# World
# Hello
# World

还有一种[*]的表示法,和[@]的区别是,[*]把所有元素都拼接成一个变量。

for STR in "${MY_ARRAY[*]}"; do #错误!所有字符串都会被合并成一长串
    echo $STR
done

# Output:
# Hello World Hello World

记住!一般来说"${MY_ARRAY[@]}"才是符合大部分人对数组行为预期的做法。 其他几种情况,可以遇到特殊需要的时候用。

#获取变量长度

下面代码,获取字符串变量长度,

STR=Hello
echo ${#STR}
5

还可以获取数组长度,还是刚才test_array的例子,

test_array=(aaa bbb ccc ddd eee)
test_array[10]=iii
echo ${#test_array}
10

数组长度由它的最后一个非空元素的位置决定。中间没有赋值的元素都为空。

向函数,命令,脚本传递参数

比如我的脚本分别打印$0,$@$#,取名为run

#! /bin/sh
echo $0
echo $@
echo $#

运行命令,

run aaa bbb ccc ddd eee fff

得到如下结果,

run
aaa bbb ccc ddd eee fff
6

Loop

if条件判断

基本的if条件判断用方括号[]。需要注意,方括号[]和变量之间一定要有空格

if [ $N = 237 ]; then #方括号和变量之间一定要有空格
  echo "Find 237!"
fi

下面是几种常用的比较符号,

comparison    Evaluated to true when
$a -lt $b    $a < $b
$a -gt $b    $a > $b
$a -le $b    $a <= $b
$a -ge $b    $a >= $b
$a -eq $b    $a is equal to $b
$a -ne $b    $a is not equal to $b
comparison    Evaluated to true when
"$a" = "$b"     $a is the same as $b
"$a" == "$b"    $a is the same as $b
"$a" != "$b"    $a is different from $b
-z "$a"         $a is empty

for循环语句,

NUMBERS=(951 402 984 651 360 69 408 319 601 485 237)
for N in ${NUMBERS[@]}; do
    echo $N
done

while循环,

COUNT=4
while [ $COUNT -gt 0 ]; do
  echo "Value of count is: $COUNT"
  COUNT=$(($COUNT - 1))
done

until循环,

COUNT=1
until [ $COUNT -gt 5 ]; do
  echo "Value of count is: $COUNT"
  COUNT=$(($COUNT + 1))
done

Case

语法如下,

function ENGLISH_CALC {
    case "$2" in
      "plus" ) echo "$1 + $3 = $(($1+$3))" ;;
      "minus" ) echo "$1 - $3 = $(($1-$3))" ;;
      "times" ) echo "$1 * $3 = $(($1*$3))" ;;
    esac
}

我输入,

ENGLISH_CALC 3 plus 5
ENGLISH_CALC 5 minus 1
ENGLISH_CALC 4 times 6

得到计算结果,

3 + 5 = 8
5 - 1 = 4
4 * 6 = 24

Function

function HELLO_WORLD {
    echo "Hello${1}"
}

HELLO_WORLD Ronald

# Output: Hello Ronald

命令列表

开胃菜

在文件系统里畅游

创建,移动和删除文件

获得命令的信息

IO重定向

符号展开

参数

练习

Hello, World!

代码如下,

#!/bin/bash
# Text to the right of a '#' is treated as a comment - below is the shell command
echo 'Hello, World!'

Variables

date -d "$date1" +%A

解答如下,可以用$()

#!/bin/bash
# Change this code
BIRTHDATE='Jan 1 2000'
Presents=10
BIRTHDAY=$(date -d "$BIRTHDATE" +%A)

也可以用两个倒引号,

#!/bin/bash
# Change this code
BIRTHDATE="Jan 1 2000"
Presents=10
BIRTHDAY=`date -d "$BIRTHDATE" +%A`

Array

#!/bin/bash
NAMES=( John Eric Jessica )

# write your code here
NUMBERS=( 1 2 3 )
STRINGS=( hello world )
NumberOfNames=${#NAMES[@]}   # 注意这里的@
second_name=${NAMES[1]}

Basic Operators

#!/bin/bash
COST_PINEAPPLE=50
COST_BANANA=4
COST_WATERMELON=23
COST_BASKET=1
TOTAL=$(($COST_PINEAPPLE + 2 * $COST_BANANA + 3 * $COST_WATERMELON + $COST_BASKET))
echo "Total Cost is $TOTAL"

Basic String Operations

注意,expr index不能匹配字符串。它找的是任意一个char的第一次出现的位置。 expr index "abc" "b"找的是babc里第一次出现的位置。expr index "abc" "ab"找的是a或者babc里第一次出现的位置。

#!/bin/bash

BUFFETT="Life is like a snowball. The important thing is finding wet snow and a really long hill."
# write your code here
ISAY=$BUFFETT
ISAY=${ISAY[@]/snow/foot}
ISAY=${ISAY[@]/snow/""}
ISAY=${ISAY[@]/finding/getting}
pos=$(expr index "$ISAY" 'w')  
ISAY=${ISAY:0:$(($pos+2))}
=======
#### Decision Making
* Change the variables in the first section, so that each if statement resolves as True.

```bash
#!/bin/bash
# change these variables
NUMBER=16
APPLES=16
KING=LUIS
# modify above variables
# to make all decisions below TRUE
if [ $NUMBER -gt 15 ] ; then
  echo 1
fi
if [ $NUMBER -eq $APPLES ] ; then
  echo 2
fi
if [[ ($APPLES -eq 12) || ("$KING" = "LUIS") ]] ; then
  echo 3
fi
if [[ $(($NUMBER + $APPLES)) -le 32 ]] ; then
  echo 4
fi

Loops

#!/bin/bash
NUMBERS=(951 402 984 651 360 69 408 319 601 485 980 507 725 547 544 615 83 165 141 501 263 617 865 575 219 390 237 412 566 826 248 866 950 626 949 687 217 815 67 104 58 512 24 892 894 767 553 81 379 843 831 445 742 717 958 609 842 451 688 753 854 685 93 857 440 380 126 721 328 753 470 743 527)

# write your code here
for N in ${NUMBERS[@]}; do
  if [ $N = 237 ]; then
    break
  fi
  if [ $(($N % 2)) = 0 ]; then
    echo $N
  fi
done

Function

‘3 plus 5’, ‘5 minus 1’ or ‘4 times 6’ and print the results as: ‘3 + 5 = 8’, ‘5 - 1 = 4’ or ‘4 * 6 = 24’ respectively.

#!/bin/bash
# enter your function code here
function ENGLISH_CALC {
  case "$2" in
    "plus" ) echo "$1 + $3 = $(($1+$3))" ;;
    "minus" ) echo "$1 - $3 = $(($1-$3))" ;;
    "times" ) echo "$1 * $3 = $(($1*$3))" ;;
  esac
}

# testing code
ENGLISH_CALC 3 plus 5
ENGLISH_CALC 5 minus 1
ENGLISH_CALC 4 times 6