Linux 33 Bash

00基础知识

shell字面意思是外壳,刚好与操作系统的内核相对应,它为用户提供了一个访问操作系统服务的接口。
bash是GNU/Linux系统中默认的shell, 它完全兼容sh,这意味着最新sh语法所写的shell脚本可以不加修改地直接使用bash执行。此外,bash吸收借鉴了csh和ksh中的一些特性,比如作业控制、命令行历史等。
shell分为两种运行模式:交互式shell和非交互式shell。交互式shell就是在终端上执行,shell接受从键盘的输入并执行。非交互式shell是指shell执行文件中的命令。
shell也提供了一些内置的命令(称为builtins)实现了一些功能,比如cd,break,continue,exec等。
查看shell内置命令
compgen -b
与任何高级语言一样,shell提供变量,流控制结构,引用和函数。Shell提供了专门用于交互式使用的功能,而不是增强编程语言。这些交互式的功能包括作业控制,命令行编辑,命令历史和别名。
交互式:
在终端中输入
echo "helloworld"
非交互式:
创建一个文件hello.sh

#!/bin/bash
echo "Hello,world"

添加可执行权限
chmod +x ./hello.sh
执行
./hello.sh

出现bad interpreter:No such file or directory(没有那个文件或目录)的原因,是文件格式的问题。这个文件是在Windows下编写的。换行的方式与Unix不一样,但是在vim下面如果不Set一下又完全看不出来.
在windows下编辑的脚本,所以有可能有不可见字符。脚本文件是DOS格式的即每一行的行尾以\r\n来标识, 其ASCII码分别是0x0D, 0x0A.
可以有很多种办法看这个文件是DOS格式的还是UNIX格式的, 还是MAC格式的:vim filename ,然后用命令 :set ff,可看到dos或unix的字样,如果的确是dos格式的, 那么用set ff=unix把它强制为unix格式的,然后存盘退出后就可运行。

01基本定义

POSIX:一系列基于Unix的开放系统标准。 Bash主要关注posix 1003.1标准的Shell和Utilities部分。
blank:空格或tab字符
builtin:由shell本身在内部实现的命令,而不是由文件系统中的某个可执行程序实现的命令。
control operator: 执行控制功能的记号。 它是换行符或以下之一:‘||’, ‘&&’, ‘&’, ‘;’, ‘;;’, ‘;&’, ‘;;&’, ‘|’, ‘|&’, ‘(’, or ‘)’
exit status:命令返回给调用者的值。 该值限制为8位,因此最大值为255。
field:一个文本单元,它是一个shell扩展的结果。 扩展后,执行命令时,生成的字段将用作命令名称和参数。
filename:用于标识文件的字符串。
job:组成一个管道的进程组以及其任一子进程,这些进程都处于同样的进程组。
job control:用户可以选择性地停止(暂停)和重新启动(恢复)进程执行的机制。
metachracter:一个分隔单词的字符(不加引号时)。 元字符是空格,制表符,换行符或以下字符之一:‘|’, ‘&’, ‘;’, ‘(’, ‘)’, ‘<’, or ‘>’。
name:由字母,数字和下划线组成并且以字母或下划线开头的单词。 名称用作shell变量名和函数名。 也称为标识符。
operator:控制操作符或重定向操作符。 运算符包含至少一个不带引号的元字符。
process group:一个具有相同进程组ID的相关进程的集合。
process group ID:在其生命周期内表示进程组的唯一标识符。
reserved word:一个对shell有特殊意义的词。 大多数保留字引入了shell流控制结构,例如for和while。
return status:exit status的同义词。
signal:一种机制,通过该机制,内核可以通知系统中发生的事件。
special builtin:shell内置命令,由posix标准分类为特殊命令。
token:shell的一系列字符被认为是一个单元。 它既可以是单词,也可以是运算符。
word:由shell处理为一个单元的字符序列。 单词可能不包括不带引号的元字符。

02变量

变量名:也称标识符,命名方式和C类似。
变量类型:强类型语言要求用户在定义变量时必须明确指定其数据类型,如Java和C++。弱类型语言不要求用户明确指定变量的数据类型,用户可以将任意类型的值赋值给变量,且变量的数据类型之间的转换不需要明确进行,如VB、JavaScript等。shell是一种动态弱类型语言,在shell中变量的数据类型不需要显示声明,变量的数据类型会根据操作变化。更确切地说,Shell中的变量是不区分数据类型的,统一按照字符串存储,但是可以根据具体语境,允许执行不同的操作。
变量定义:Shell中用户可以直接使用变量,不需要预先定义。但是shell提供了两个内置命令(builtin),declare和typeset帮助我们声明变量和/或设置变量的属性。这两个命令其实是等价的,只是typeset是obsolete(废弃),declare是在bash2及之后的版本中引入的。
使用格式如下:

declare [-aAfFgilrtux] [-p] [name[=value] ...]
typeset [-aAfFgilrtux] [-p] [name[=value] ...]

选项描述:
-a 声明的变量用作索引数组(数组索引为整数),declare -a name
-A 声明的变量用作关联数组(数组索引为字符串),declare -A name
-f 声明的变量用作函数名,declare -f funName,若给出的函数名已经定义,则列出函数体;若之后未给出函数名,则会列出此前所有定义的函数
-F 仅用于显示之前定义的函数名,每行一个
-g 声明的变量名为全局变量,declare -g name
-i 声明的变量为整数
-l 在赋值时将变量名转换为小写(这个没搞懂)
-r 设置变量为只读,应该在声明该变量的同时赋值,declare -r name=value
-t 为变量名称指定trace属性。 跟踪函数从调用shell继承DEBUG和RETURN陷阱。 trace属性对变量没有特殊含义。
-u 在赋值时将变量名转换为大写(这个也没明白)
-x 导出变量名,即设置为环境变量,在之后的shell中可用
-p 显示变量的属性和值
取消一个变量的某个属性使用 declare +option name
作用域:全局变量,使用的范围比较大,不局限于某个局部使用。默认shell中的变量都为全局变量,其作用域从定义的地方开始,知道shell脚本结束或者被显式删除。
局部变量,使用范围较小,通常仅限于某个程序段访问,如函数内部。使用local关键字定义局部变量,且函数的参数都是局部变量。函数内部的局部变量会屏蔽外部同名的全局变量。
赋值
在shell中变量的赋值类似与如下键值对的格式:name=value
一般value都被视为字符串,若value中含有空白字符,则需要用单引号或者双引号或者反引号将其引起来。三者的区别在于:单引号剥夺特殊符号的含义,双引号中可以使用参数替换或者命令替换以及其他的特殊字符的含义,反引号的内容会被视为命令执行。注意等号两侧不能有空白符,因为shell中以空白符作为分界符。

# 仅针对bash,其他版本的shell如zsh、ksh、csh、fish等结果可能不一样
var=1 # 正确的格式
var='hello world' # 正确的格式
var="Hello,world" # 正确格式
var=`ls -l` # 正确的格式
var = value # 这种形式会把var作为一个命令, = value作为两个参数传递给它
var= value # 这种形式位var赋值为空,把value作为单独的命令执行
var =value #这种形式把var作为一个命令,=value作为参数传递给它

引用变量的值,使用如下格式${name},花括号可以省略,但是区分变量名的时候必须时候用。

var=1
echo $var
#结果:1
var="hello "
echo ${var}world
#结果:hello world

清除:
使用unset清除不需要的变量,格式为:unset name

var="hello"
echo $var
#结果:hello
unset var
echo $var
#结果:

03条件语句

Shell提供了一系列的条件测试来处理程序执行过程中的各种情况,并做进一步的操作。
在shell中提供了两个内置命令"[“和test用于计算条件表达式的值,”[“和test其实是同义词,区别在于使用”[“时必须最后用”]"匹配。

var=10086
# 注意这里[ ]两侧必须有空格
if [ $var -eq 10086 ]
then
    echo var is 10086
else
    echo var is not 10086
fi

var=10010
if test $var -eq 10010 
then
    echo var is 10010
else
    echo var is not 10010
fi

值得一提的是,在Shell程序中,条件测试的指定条件为真时,返回值为0,否则返回非零值。这个设计刚好和C语言中的判断相反,这主要是为了和Shell程序的退出状态一致。当Shell程序执行成功后,返回0;发生错误,则返回非零值。

在Shell中$?是一个特殊的系统变量,表示上一条语句执行的返回值。

var=1
test $var -eq 1; echo $?
test $var -eq 2; echo $?

test命令的使用

文件操作符:
test -a FILE 测试文件是否存在
test -b FILE 测试文件是否是块特殊文件
test -c FILE 测试文件是否是字符特殊文件
test -d FILE 测试文件是否是目录
test -e FILE 测试文件是否存在
test -f FILE 测试文件是否存在并且是普通文件
test -g FILE 测试文件是否设置了组ID
test -h FILE 测试文件是否是符号链接
test -L FILE 测试文件是否是符号链接
test -k FILE 测试文件是否设置了sticky位
test -p FILE 测试文件是否是命名管道
test -r FILE 测试文件是否是对当前用户可读
test -s FILE 测试文件是否存在且非空
test -S FILE 测试文件是否是socket
test -t FD 测试FD是否在终端中打开了
test -u FILE 测试文件是否设置了用户ID
test -w FILE 测试文件是否对当前用户可写
test -x FILE 测试文件是否对当前用户可执行
test -O FILE 测试文件是否是实际由你拥有
test -G FILE 测试文件是否是实际上由你所在的组拥有
test -N FILE 测试文件是否在最后字词读取后被修改了
test FILE1 -nt FILE2 根据最后修改时间判断FILE1是否比FILE2更新
字符串操作符:
test -z STRING 字符串是否非空
test -n STRING
test STRING1 = STRING2 字符串是否相等
test STRING1 != STRING2 字符创是否不等
test STRING1 < STRING2 按照字母表顺序,STRING1是否小于STRING2
test STRING1 > STRING2 按照字母表顺序,STRING1是否大于STRING2
整数操作符:
test number1 -eq number2 是否相等
test number1 -ne number2 是否不等
test number1 -gt number2 number1是否大于number2
test number1 -lt number2 number是否小于number2
test number1 -ge number2 number1是否大于等于number2
test number1 -le number2 number是否小于等于number2
逻辑操作符:
test ! EXPR 对表达式取反
test EXPR1 -a EXPR2 相当于and,两个表达式都为真则为真
test EXPR1 -a EXPR2 相当于or,有一个表达式为真即为真
其他操作符:
test -o OPTION shell选项OPTION是否允许
test -v VAR 是否设置了shell变量VAR
test -R VAR 是否设置了shell变量VAR,且VAR是一个名字引用

[[ ]]的使用

[[ ]]是bash中的关键字,相当于new test,也是用于计算表达式的值,但是具有更加强大的功能。
简单来说,test([ ])实现了老的可移植的语法特性。几乎所有的shell总都支持它,而[[ ]]是一个新的关键字(不是builtin)。在shell中[ ]和[[ ]]基本功能有很多相似之处,不同在于[[ ]]提供了更多方便使用的特性。
比如字符串比较,在[ ]中 < 和 >会被解释为重定向输入输出,要想使用比较的功能需要使用\转义,而在[[ ]]可以直接使用。
[[ ]]中可以直接使用&&(条件与)和||(条件或)连接多个条件表达式。
注:这里的&&和||也是类似于C语言中的短路求值,当第一个表达式可以确定最终的值得时候,就不会计算第二个表达式的值。

[[ 2 > 1 && 3 > 2 ]]; echo $?
# 输出为:0
[[ 2 > 1 && 3 > 4 ]] ; echo $?
# 输出为:1
[[ 2 > 1 || 3 > 4 ]] ; echo $?
# 输出为:0

另一个微妙的区别在于[[ ]]内不存在单词分割或者通配符展开,这样在对某些变量进行判断时就不需要用引号引用了。

touch hello\ world.txt
var=hello\ world.txt
[ $var = "hello" ]; echo $?
#输出结果:bash: [: 参数太多 2
[ "$var" = "hello world.txt" ]; echo $?
#输出结果:0
[[ "$var" = "hello world.txt" ]]; echo $?
#输出结果:0

if语句

bash中的if条件语句和一般的编程语言没有太大区别,基本语法如下:
if 命令; then 命令; [ elif 命令; then 命令; ]... [ else 命令; ] fi
首先执行if中的命令,如果退出状态为0,则执行then后的命令,然后终止;否则依次执行elif后的命令(elif 执行完后还未终止,则执行else后的命令),直到退出状态为0,终止命令。

var="hello"
if [[ $var = "hell" ]];
then
    echo "var is hell";
elif [[ $var = "world" ]]
then
    echo "var is world";
elif [[ $var = "hello" ]]
then
    echo "var is hello";
else
    echo "var is ";
fi

case语句

case语句专门用于处理多分支结构的情况。case采取了模式匹配处理不同的情况。基本语法如下:

case exp in 
case) 
commands ;; 
case) 
commands ;; 
... 
case) 
commands ;; 
*)
commands ;;
esac

其中方括号代表匹配其中的任一字符,连接字符表示的是两个字符之间的所有字符(包含左右端点),*代表匹配任意长度的字符串。所以,最后的*)语句代表处理剩余的所有情况,类似于C语言中的default。

04运算符

赋值运算:
=,通用赋值运算符,适用于算术和字符串赋值。

var=2
echo $var
var="hello world"
echo $var

算术运算:
+ - * / % **(乘方,bash2.02引入) += -= *= /= %=

bash中不能直接进行算术表达式的计算,算术运算符通常出现在expr(来自于coreutils,外部程序)或let(bash的内置命令)表达式中。对于整数运算还可以使用(())或[]。expr和let也只能计算整数运算,如果要进行浮点数运算使用bc。

var=2+1
echo $var
#结果:2+1
#使用let计算
let "var = 1 + 2 * 3 - 5 % 2"
echo $var
#结果:6
expr 1 + 1
#结果:2
echo $((3*2-1+4))
#结果:9
echo $[12+32/4]

位运算:
<<,<<= >>,>>= &,&= |,|= ~,~= .=(按位异或)
自增自减运算:
var++ ++var var-- --var
逗号运算符:
逗号运算符将两个或多个算术运算链接在一起.

v1=1
v2=2
let v1++,v2++
echo $v1 $v2
#结果为:2 3

数字进制:
bash中数字默认是十进制得,但是也可以使用八进制或十六进制,八进制以0开头,十六进制以0x或0X开头.

let var=0x10086
echo $var
#结果:65670
let var=0100
echo $var
#结果:64

05循环语句

在Bash中,循环结构可以使用for、while,以及until等语句实现。此外,可以使用continue和break语句来跳过或退出循环体中的部分语句。

for循环

for循环通常用于遍历字符串中的单词或者列表。bash中的for循环语句可以分为带列表的for循环、不带列表的for循环,以及类C风格的for循环。

带列表的for循环

带列表的for循环通常用于将一组语句执行已知的次数,基本语法如下:

for var in {list}
do
    statements;
done

这里var值循环变量,{list}代表一系列的数字或者字符串,以空格间隔。do和done之间的为循环体,循环时会重复执行。执行过程为bash会将{list}中的元素从头至尾逐个赋值给var,然后执行循环体,执行完后,执行done后的语句。
示例:

for i in 1 2 3 4 5 6 7 8 9 10
do
    echo -n "$i "
done

for str in us zh de
do
    echo -n "$str "
done

bash中提供了一种叫做大括号扩展的功能帮助我们简单产生列表,这里演示简单的用法。语法为:{begin…end[…step]} 其中begin和end制定了开始和结束的字符(均包含在内),可选的step制定了步长。注意:begin和end只能是单个字符或者整数,step只能是整数,它们都不能是变量。

# 不指定步长的话默认为1
for i in {1..12}
do
    echo -n "$i "
done
# 结果:1 2 3 4 5 6 7 8 9 10
for i in {0..100..10}
do
    echo -n "$i "
done
# 结果:0 10 20 30 40 50 60 70 80 90 100

还能够进行拼接

for file in test{a..z}.txt
do
    echo $file
done
不带列表的for循环

for循环的列表可以省略,称为不带列表的for循环语句。默认遍历命令行参数。
创建一个shell文件,命名比如for.sh,文件内容如下:

#!/bin/bash
for arg
do
    echo $arg
done

执行:
bash for.sh 1 2 3 4 5 6
1 2 3 4 5 6就是命令行参数,会依次打印出来。

类C风格的for循环

基本语法如下:

for ((expr1; expr2; expr3))
do
    statements;
done

for循环语句的执行条件被两个圆括号包括。执行条件分为有分号分隔三部分,一般来说,第一部分expr1位条件变量初始化的语句;第二部分为判断语句;第三部分expr3用来改变条件变量的值。

for ((i=0; i<10; i++))
do
    echo $i
done

until循环

until循环语句是将循环体重复执行,直到条件成立为止。也就是说条件不成立则执行循环体,条件成立则跳出循环体。
基本语法如下:

until expr
do
    statements
done

每次执行循环体之前,until语句会先判断expr的值,若非0,则执行循环体,否则退出循环。
示例:

i=1
until [[ i -gt 10 ]]
do
    echo $i
    let i++
done

while循环

while循环语句可以在条件成立的情况下重复执行循环体(until是条件成立跳出循环体)。
基本语法如下:

while expr
do
    statements
done

示例:

i=1
while [[ i -lt 10 ]]
do
    echo $i
    let i++
done

break和continue

break用于跳出循环,continue用于跳过某些语句,执行下一次循环。两者都是用于for、until和while循环中的。

06函数

使用函数可以对程序进行更好的组织,将一些相对独立的代码变成函数,可以提高程序的可读性和重用性,避免重复编写大量相同的代码。
函数定义:
简单来说,函数就是将一组功能相对独立的代码集中起来,形成一个代码块,这个代码块可以完成某个具体地任务。本质上来说,函数是一个函数名到某个代码块的映射。当定义了函数之后,就可以通过函数名来调用其所对应的代码块。
对于shell函数而言,函数与shell脚本并没有明显的区别,shell脚本中可以使用的命令和语句,都可以在函数中使用。但是,在执行时,函数和脚本还是有着本质上的区别。shell脚本在执行时,会创建一个新的shell进程来解释执行脚本中的代码,脚本执行完成,shell进程就会结束;而函数在执行时,系统并不会为其单独创建一个进程,而是在调用者的进程中直接解释执行函数中的代码。
基本语法:

function name (){
    commands;
}
// 或者可以省略function
name () {
    commands;
}

函数名的命名规则和变量的命名规则相同,由数字、字母或下划线组成且以字母或下划线开头的字符串。
示例:

function Hello(){
    echo "Hello world"
}
# 执行
Hello

传递参数:
当某个函数定义之后,用户就可以通过函数名来调用该函数。调用函数的基本语法如下:

func argv1 argv2 ...    

其中func表示函数名,argv1、argv2等表示传递给函数的参数。
在函数中可以通过一些特殊的变量来访问传递过来的参数:
$n,表示传递给函数的第n个参数,$1表示第一个参数等
$#,表示传递参数的总个数
$*,表示以"argv1 argv2 …“的形式返回所有参数的值
S@,表示以"argv1” “argv2” …的形式返回所有参数的值
示例:

function hi() {
    echo $#
    echo $*
}
# 执行:hi 1 2 3 4 5 6
hi 1 2 3 4 5

函数返回值:
在shell中,处理函数的返回值可以分为以下几种情况来讨论。
使用return语句返回某个数值,但是只能返回0~255之前的整数值。
将返回的数据输出到标准输出,比如使用echo命令。
示例:

function getStr() {
    echo "hello"
}
# 使用:
str=$(getStr)
echo $str
# 结果:hello

函数库文件:
为了方便地重用函数功能,可以创建函数库文件,保存常用的函数。
定义:函数库文件类似于一个脚本文件,区别在于库文件通常只含有函数定义,而脚本文件中则包含函数和变量的定义,以及可执行的代码(函数外部的代码)。
示例:如下所示的脚本文件

#!/bin/bash
function out() {
    echo "$1"
}

str="Hello, world"
out "$str"

以上是函数的用法。
现在改为函数库,新建一个函数库文件,命名为libout.sh

#!/bin/bash
function out() {
    echo "$1"
}

在终端执行

# 加载库文件
. libout.sh
out "Hello, world"
# 结果:Hello,world

递归函数:
Bash支持函数的递归调用,即函数可以直接或者间接地调用自身。
示例:该函数递归调用自身输出i的值,直到i>10

function fun() {
    i=$1
    echo "i is $i"
    let i++
    if test $i -gt 10
    then
    return 0   
    fi
    fun $i
}

fun 1

07数组

数组定义

是指将具有相同类型的若干变量按照顺序组织起来的数据类型。
在之前的变量一节,提到过可以使用declare或者typeset定义一个变量为数组。或者可以直接指定数组中的元素值来定义一个新的数组变量。
bash中的数组下标从零开始。

# 定义arr为索引数组
declare -a arr
# 定义arr为关联数组
declare -A arr
# 为arr[0]赋值
arr[0]=10
# 为arr[hello]赋值
arr[hello]="world"

使用数组中的值,格式:
${myarray[index]},$myarray相当于${myarray[0]},即数组的第一个元素
以元素值集合的形式为数组赋值,格式:
arr=( var0 var1 var2 ... varn )

示例:

arr=(1 2 3 4 5 6)
# 输出所有元素,两种方式都可以,但是有区别
echo "${arr[@]}"
echo "${arr[*]}"

通过键值对的形式为数组赋值,格式:
arr=([0]=var1 [1]=var2 ... [n]=varn)
示例:

arr=([0]=100 [1]=200  [2]=300)
echo ${arr[*]}

若采用这种形式为关联数组赋值,需要先用declare定义

declare -A myArray
myArray=([Unicom]=10010 [TELECOM]=10000 [Mobile]=10086)
echo ${myArray[*]}

访问数组

通过KaTeX parse error: Expected '}', got '#' at position 2: {#̲array[@]}或{#array[*]}来获取一个数组的长度。

array=(0 1 2 3 4 5)
echo ${#array[@]}
# 输出:6
echo ${#array[*]}
# 输出:6

简单的遍历索引数组:

array=(0 1 2 3 4 5)

for ((i=0;i<${#array[*]};i++))
do
    echo ${array[$i]}
done
array=(0 1 2 3 4 5)
for i in ${array[*]}
do
    echo $i
done

遍历关联数组:
通过 ! m y a r r a y [ @ ] 注 : 不 能 用 {!myarray[@]} 注:不能用 !myarray[@]{!myarray[*]}

declare -A myarray
myarray[a]=0
myarray[b]=1
myarray[c]=2
for i in "${!myarray[@]}"
do
  echo "key  : $i"
  echo "value: ${myarray[$i]}"
done

在数组末尾追加元素:

declare -a array
array=(0 1 2 3 4 5 6)

for i in {7..10}
do
        array[${#array[*]}]=$i
done
echo ${array[@]}

获取部分数组元素:
可以通过切片获取部分数组元素,格式:
${array[@|*]:start:length}
示例:

unset nums
nums=(0 1 2 3 4 5 6 7)
newnums=${nums[@]:1:4}
echo ${newnums[@]}

替换删除数组元素:
数组元素替换,可以将某个数组元素的部分内容用其他字符来替代,基本语法:
${array[@|*]/pattern/replacement}
示例:

unset array
array=("test1.txt" "hello.txt" "word.txt")
echo "${array[@]/.txt/.c}"
# 输出:test1.c hello.c word.c

使用unset可以删除数组中的某个元素或者整个数组
删除数组中的某个值:
unset array[n]
删除整个数组:
unset array

08输入输出重定向

输出基本概念

在任何一种操作系统中,程序开始读写一个文件的内容前,必须首先在程序和文件之间建立联系或者通信通道,这一过程称为打开文件。Linux系统中有两种机制用户描述程序与文件的这种连接:文件描述字和流。相应地,系统中关于I/O的操作也分为两大类。
文件描述字表示为int类型的对象,而流表示为指向FILE结构体的指针。
最常见到的文件描述符是 stdin, stdout 和 stderr,文件描述符的数字分别是0,1和2。

[root@ bash]# ls -l /dev/std*
lrwxrwxrwx 1 root root 15 May  9 10:32 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 May  9 10:32 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 May  9 10:32 /dev/stdout -> /proc/self/fd/1

输入输出重定向

默认执行命令的输入是从标准输入读取,正确结果是输出到标准输出,错误输出到标准错误的。
我们可以通过>、>>、<、<<来重定向标准输入和标准输入到文件。
命令格式:

command > filename # 重定向标准输出到文件filename,会清空原有文件内容
command >> filename # 重定向标准输出到文件filename,输出结果附加到原文件内容最后
command < filename # 重定向文件filename为标准输入

示例:

pwd > pwd.txt
cat pwd.txt

ls / > ls.txt
cat ls.txt
# >>用法一样,只是不会覆盖重定向文件原来的内容

还可以将某个文件描述符重定向。
格式:
command fd > filename
将标准错误输出重定向到某个文件:

rm a.txt # 确保当前目录下无a.txt这个文件
rm a.txt 2> error.txt # 2就是stderr的fd
cat error.txt

09帮助信息

遇到问题使用搜索引擎前,我们可以先试试这些方法。
###whatis
使用whatis可以查看命令的简要说明,每个手册页中都有一个概述。whatis 搜索手册页名称并显示任何与 名称 匹配的手册页描述。
使用格式:
whatis keyword

[root@ bash]# whatis pwd
pwd (1)              - print name of current/working directory

man

man - 在线参考手册的接口
man是系统的手册分页程序。指定给man的页选项通常是程序、工具或函数名。程序将显示每一个找到的相关手册页。如果指定了 章节,man 将只在手册的指定章节搜索。默认将按预定的顺序查找所有可用的章节(默认是“1 n l 8 3 2 3posix 3pm 3perl 5 4 9 6 7”,除非被 /etc/manpath.config 中的 SECTION 指令覆盖),并只显示找到的第一个页,即使多个章节中都有这个页面。
下面显示了手册的 章节 号及其包含的手册页类型。
1 可执行程序或 shell 命令
2 系统调用(内核提供的函数)
3 库调用(程序库中的函数)
4 特殊文件(通常位于 /dev)
5 文件格式和规范,如 /etc/passwd
6 游戏
7 杂项(包括宏包和规范,如 man(7),groff(7))
8 系统管理命令(通常只针对 root 用户)
9 内核例程 (非标准)
详细使用方法可以采用man man查看。

which和whereis

which可以查看可执行文件的路径。which -a name可以查看所有匹配的可执行文件路径。

[root@ bash]# which which
/usr/bin/which
[root@ bash]# which echo
/bin/echo
[root@ bash]# which -a which
/usr/bin/which
/bin/which

whereis定位命令的二进制文件、源文件以及手册页的路径。

[root@ bash]# whereis which
which: /usr/bin/which /bin/which /usr/share/man/man1/which.1.gz
[root@ bash]# whereis whereis
whereis: /usr/bin/whereis /usr/share/man/man1/whereis.1.gz

使用–help或者-h

适用于外部命令,格式:
command --help 或者command -h

ls --help

##10理解shell
Bash是shell的一种,shell是一个复杂的交互式程序。搞清楚shell进程以及它与系统之间的关系有助于我们更好地理解shell。

shell的类型

系统启动什么样的shell程序取决于你的个人配置。这一配置记录在/etc/passwd文件中。使用如下命令可以查看。

[root@ bash]# cat /etc/passwd | grep `whoami`
root:x:0:0:root:/root:/bin/bash

cat命令读取文件/etc/passwd的内容,并将它输出到标准输出,然后通过管道将输出传递给grep命令进行搜索指定的字符串并将包含字符串的那行文本输出到标准输出,whoami输出当前登录的用户名。
最后一个字段列出了默认的shell程序。当用户登录到某个虚拟控制台终端或是在GUI中启动终端仿真器(Terminal),默认的shell程序就会开始运行。
另外,我们的系统中可能安装有多个不同类型的shell,比如bash、dash、zsh、csh等。我们可以在/etc/shells文件中查看。

[root@ bash]# cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/tmux
/usr/bin/screen

我们可以看到本系统(Linux Ubuntu 4.4.0-62-generic)中有四个不同的shell:sh、dash、bash、rbash。这四个shell都在/bin目录下,我们来仔细看看这四个文件。

[root@ bash]# ls -l /bin/*sh
-rwxr-xr-x 1 root root 1109564 Jun 24  2016 /bin/bash
-rwxr-xr-x 1 root root  173644 Feb 18  2016 /bin/dash
lrwxrwxrwx 1 root root       4 Apr  7  2017 /bin/rbash -> bash
lrwxrwxrwx 1 root root       4 Apr  7  2017 /bin/sh -> dash
lrwxrwxrwx 1 root root       7 Aug 19  2015 /bin/static-sh -> busybox

我们发现rbash是指向bash的软连接,sh是指向dash的软连接。
bash我们已经了解不少了,dash是什么呢?
Debian Almquist Shell (dash)是一款兼容 POSIX 的 shell,衍生自 ash。由于它执行脚本比 bash 快,依赖的库少(使得它更稳健,受软硬件故障影响少),Debian 使用它作为默认的系统 shell。而Ubuntu是基于Debian的发行版,所以也就沿用了Debian的设计。
dash与bash相比具有更快的速度,更少的依赖,但是交互能力较差,一般只用于执行shell脚本而非用作交互式shell。

shell的父子关系

在shell中可以继续执行shell,新的shell程序称为子shell原有的shell称为父shell。
当输入bash生成子shell时,看不到什么变化,但是可以使用ps命令查看进程。

[root@ bash]# ps
  PID TTY          TIME CMD
18300 pts/0    00:00:00 bash
18466 pts/0    00:00:00 bash
18477 pts/0    00:00:00 ps

可以明显观察到新的bash和原有的bash,此时可以输入exit退出当前shell,退出最后一个shell会导致终端关闭。生成的子shell进程会从父shell进程中复制部分环境。
子shell中可以继续创建shell,示例:

[root@ bash]# ps
  PID TTY          TIME CMD
18300 pts/0    00:00:00 bash
18466 pts/0    00:00:00 bash
18478 pts/0    00:00:00 bash
18488 pts/0    00:00:00 ps
[root@ bash]# ps --forest
  PID TTY          TIME CMD
18300 pts/0    00:00:00 bash
18466 pts/0    00:00:00  \_ bash
18478 pts/0    00:00:00      \_ bash
18489 pts/0    00:00:00          \_ ps

使用ps --forest可以更方便地观察。

11环境变量

环境变量可以帮助提升你的Shell体验。很多程序和脚本都通过环境变量来获取系统信息,存储临时数据和配置信息。

什么是环境变量

bash使用环境变量( environment variables)来存储有关shell会话和工作环境的信息。这项功能允许你在内存中存储数据,以便程序或shell脚本能够轻松访问。这是存储持久数据的一种简便方法。bash的环境变量分为两类:全局变量和局部变量。
补充:bash使用一致的专有环境变量,但是不同发行版的linux可能会添加自己的环境变量。

全局环境变量

全局变量对于shell会话和所有生成的子shell都是可见的。全局环境变量有助于子shell获取父shell信息。
在Linux中有四个命令可以帮助我们查看所有的环境变量,printenv、env、set、declare。

局部环境变量

局部变量只能在定义它们的进程中可见,Linux也有默认定义的标准局部环境变量。你也可以自己定义局部环境变量,称为用户定义局部变量。
set命令会显示某个特定进程的所有环境变量,包括局部变量、全局变量以及用户定义变量。
补充:set、env、printenv之间的区别很细微。set命令会显示出所有变量,并且按照字母顺序排序。env和printenv输出相同,但是仅含有全局变量,并且未对变量排序。

设置用户定义变量

设置局部用户定义变量:
就是之前02一节提到的变量。
my_var=10086

一般bash的环境变量都是使用的大写字母,建议自己的变量名为小写,避免产生冲突。

设置全局环境变量:
先创建一个局部环境变量,然后使用export命令将其导出到全局变量。

myvar=10086
# 启动子shell
bash
echo $myvar # 输出为空白的一行
# 退出子shell
exit
# 导出为全局变量
export myvar # 注意这里不加$
# 再次启动一个子shell
bash
echo $myvar # 输出为:10086

通过env命令也能看到定义的全局环境变量 myvar=10086

删除环境变量

使用unset命令,格式:
unset varname

在子shell中删除全局环境变量不会影响到父shell的全局环境变量。

默认环境变量

HOME:当前用户的主目录
IFS:shell用来将文本字符串分割成字段的一系列字符
PATH:shell用来查找命令的目录列表。当你输入一个命令时,shell会按照这个目录遍历寻找。
###系统环境变量
在登录系统启动一个bash shell时,默认情况下bash会在几个文件中查找命令。这些文件叫做启动文件或者环境文件。bash检查的启动文件取决于启动bash的方式:

  • 登录时作为默认登录shell
  • 作为非登录shell的交互式shell
  • 作为脚本运行的非交互式shell
登录shell

登录Linux系统是,bash作为登录shell启动。登录shell会一次从以下几个启动文件中读取命令。

/etc/profile
$HOME/.bash_profile
$HOME/.bashrc
$HOME/.bash_login
$HOME/.profile

/etc/profile文件是系统上默认的bash shell的主启动文件。系统上的每个用户登录时都是执行这个文件。
$HOME目录下的启动文件
剩下的启动文件都启着同一个作用:提供一个用户专属的启动文件来定义该用户所用到的环境变量。一般Linux发行版只有到下面这四个启动文件中的一两个。
$HOME/.bash_profile
$HOME/.bashrc
$HOME/.bash_login
$HOME/.profile

这里列出Ubuntu的启动文件:

[root@ /]# ls -al ~
total 39920
drwx------  8 root root     4096 May 10 10:36 .
drwxr-xr-x 22 root root     4096 Apr  7  2017 ..
-rw-------  1 root root    39818 May 10 11:28 .bash_history
-rw-r--r--  1 root root      197 Mar  1  2018 .bash_profile
-rw-r--r--  1 root root     3173 Sep 26  2017 .bashrc
-rwxr-xr-x  1 root root      493 Sep 26  2017 build_cov.sh
drwx------  2 root root     4096 Apr  7  2017 .cache
drwxr-xr-x  5 root root     4096 Sep 26  2017 coverity
drwxr-xr-x  4 root root     4096 Mar 28 11:34 coverity_result
-rwxr-xr-x  1 root root 40741163 Sep 26  2017 coverity.tar.gz
-rw-r--r--  1 root root        2 Apr 12 12:51 ip_forward~
-rw-r--r--  1 root root        2 Apr 12 17:26 ip_forwarz~
-rw-------  1 root root       28 May 10 10:36 .lesshst
drwxr-xr-x  2 root root     4096 Mar  2  2018 .nano
-rw-r--r--  1 root root      148 Aug 17  2015 .profile
drwxr-xr-x  3 root root     4096 Mar  1  2018 .subversion
drwxr-xr-x  3 root root     4096 Apr  7  2017 .vim
-rw-------  1 root root     8567 May  9 21:18 .viminfo
-rw-------  1 root root     5380 Mar  2  2018 .viminfo.tmp
-rw-r--r--  1 root root     2556 Apr  7  2017 .vimrc
-rw-------  1 root root      260 Apr 26  2018 .Xauthority

可以看到这里只有.bashrc和.profile两个文件。
shell会按照以下顺序执行启动文件,找到第一个其他的就会被忽略。
$HOME/.bash_profile
$HOME/.bash_login
$HOME/.profile

这里没有$HOME/.bbashrc,因为该文件是通过上面三个文件中的一个运行的。

我们来查看一下.profile文件,.bashrc文件请自行查看。

[root@ /]#  cat ~/.profile 
# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

mesg n || true
交互式shell

交互式shell不是登录系统时启动的,它可能是你打开终端启动的。交互式shell不会访问/etc/profile文件,而是只会检查用户HOME目录中的.bashrc文件。

非交互式shell

执行shell脚本是的非交互式shell。脚本能以不同的方式执行,可以启动子shell执行,也可以在当前shell中执行。
bash提供了一个BASH_ENV的环境变量,当shell启动一个非交互式shell进程时,它会检查这个环境边浪来查看要执行的启动文件。若有指定的文件,则shell会执行该文件。Ubuntu未设置该变量。
若BASH_ENV未设置,那么非交互式shell如何获得环境变量呢?对于以子shell进程执行的情况,子shell会继承父shell导出的变量(export)。对于在当前shell中执行的情况,变量即为当前shell的变量。

12文件管理

Unix/Linux系统中将几乎一切都是为文件,所有操作都离不开文件,因而学习管理、操作文件是极为重要的。注:目录也是文件的一种,它记录着当前目录下的文件项,但并不实际保存文件的数据。
一般我们打开终端,所处的目录默认为当前登录用户的家目录(或者叫主目录, home directory)。对于一般的用户$HOME环境变量记录了家目录的所在,一般位于/home/username,root用户的家目录是/root。shell中也使用~符号表示家目录的所在。
查看当前目录:
pwd
更改目录:
cd
cd命令依赖于CDPATH变量,CDPATH定义了搜索目录的路径,cd命令会先在CDPATH定义的路径下寻找dir,若找不到,则在当前目录下寻找,若找不到,给出信息不存在目录。CDPATH中可以有多个搜索目录,以:间隔。

若是绝对路径则会直接切换到对应的目录。

列出当前目录下的文件:
ls
-a --all :列出所有文件,包括以.开头的文件。
-A --almost-all :列出所有文件,除了.和…。
-l:使用长格式列出。
-S:按大小排序,大的在前。
-t:根据最后修改时间排序,新的在前。
创建文件:
touch
其实这个命令是用于更改文件(包括目录)的时间戳的,当文件不存在时会创建一个空白文件。若是存在的文件或目录,则会更改它的最后修改时间为当前时间。
使用stat filename可以查看文件的信息信息。
复制文件:
cp path/file1 path/file2
创建文件夹:
mkdir
移动文件夹(文件夹重命名):
mv
删除文件(强制删除-f):
rm
删除目录:
rm -r

13用户与组

Linux系统是一个多用户系统,允许多个用户同时登录到系统,使用系统资源。当多个用户同时使用系统时,需要区分不同的用户,这就需要一种秩序,规范每个用户的权限。Linux采用用户账户来区分不同的账户,账户实际上是一个用户在系统上的标识,系统根据账户ID来区分每个用户的文件、进程、任务等资源,并给每个用户提供特定的工作环境,使每个用户的工作都能独立进行。

账户系统文件

主要有:/etc/passwd、/etc/shadow、/etc/group、/etc/gshadow四个文件。
/etc/passwd:
该文件每行定义一个用户账户,一行中又换分为多个字段定义用户的不同属性,各字段之间用:分隔。

[root@ /]# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

从左到右属性依次为:
用户名字段,这是用户登录系统时使用的用户名,在系统中是唯一的。
口令字段,存放加密后的口令(密码),这里一般显示为x表示存放在/etc/shadow文件中。
用户标志号(UID),一个整数,系统内部用它来标识用户,root用户的UID为0。1~499为标准账户,普通用户账户从500开始。
组标识号(GID),一个整数,系统内部用它来标识用户所属的组。每个用户账号在建立后都会有一个主组,主组的相同的账号GID相同。默认,创建一个用户时建立一个同名的私有组,只有用户本人一个成员。
GECOS,存放用户全名、地理位置等信息。
主目录位置,存放用户登录系统后进入的目录。
默认shell

/etc/shadow:
/etc/passwd文件对任何用户都可读,加密后的密码存放在/etc/shadow文件中,只有root用户可读。

[root@ /]# cat /etc/shadow
root:$6$o5ZUXVNh$TuoOLWCjk2Oq89vR2zDGdsGGk5TV2LgQfwNcPPKGKyT/cev/D07HJ34QTOC5Xlv40g1Ca2q0xqCxytVW7au3S/:17263:0:99999:7:::
daemon:*:17212:0:99999:7:::
bin:*:17212:0:99999:7:::
sys:*:17212:0:99999:7:::
sync:*:17212:0:99999:7:::
games:*:17212:0:99999:7:::
man:*:17212:0:99999:7:::

/etc/group:
该文件记录用户所属的组,一个组可以用多个用户,一个用户也可以属于多个组。当一个用户同时属于多个组时,/etc/passwd文件中记录的是用户所属的主组,也就是登录时所属的主组,其他组称为附属组。

[root@ /]# cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog,ubuntu
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
uucp:x:10:
man:x:12:

从左到右属性依次为:
组名,该组的名称;
组口令,已经不再使用该字段保存口令,使用x占位
GID,组的标识号和UID类似
组成员,属于该组的成员,多个成员间以,间隔
/etc/gshadow:
该文件用于定义用户组口令、组管理员等信息,该文件只有root用户可以读取。

[root@ /]# cat /etc/gshadow
root:*::
daemon:*::
bin:*::
sys:*::
adm:*::syslog,ubuntu
tty:*::
disk:*::
lp:*::
mail:*::
news:*::
uucp:*::
man:*::

从左到右属性依次为:
组名,组名称
组口令,保存已经加密的口令
组的管理员账户,管理员可以对该组添加、删除账户
组成员,属于该组的用户,多个用户以,间隔

用户与组的管理

管理用户与组的命令工具:
useradd,添加新的用户
usermod,修改已经存在的指定用户
userdel,删除已经存在的指定用户
groupadd,添加新的组
groupmod,修改已经存在的组
groupdel,删除已经存在的组
查看当前已经存在的所有用户:

[root@ /]#   cat /etc/passwd | cut -f 1 -d :
root
daemon
bin
sys
sync
games
man

useradd:
useradd是一个用于创建新用户的低级使用程序,为了添加新的用户,useradd命令必须编辑/etc/passwd、/etc/shadow、/etc/group、/etc/gshadow文件,因此需要管理员权限。
简单演示添加用户,添加用户后需使用passwd username来设置用户密码后才可以登录:

sudo useradd tom
# 查看新添加的用户和默认的私有组
cat /etc/passwd | grep tom
# 输出为:tom:x:1001:1001::/home/tom:
cat /etc/group | grep tom
# 输出如下:tom:x:1001:

# 设置密码
sudo passwd tom
# 切换用户可以使用su username

注意在上面的passwd中我们可以看到tom的主目录为/home/tom,但是在/home目录下我们找不到tom的子目录,这里需要手动创建或者使用useradd -m 选项自动创建。
命令格式:

useradd [options] LOGIN
useradd -D
useradd -D [options]

选项介绍(详细信息,请参阅man useradd):
-b,–base-dir BASE_DIR 若未使用 -d HOME_DIR,则使用默认的基目录。BASE_DIR 加上账户名就是主目录。如果没有使用 -m 选项,BASE_DIR 必须已经存在。 如果选项没有指定,useradd 将使用 /etc/default/useradd 中的 HOME 变量,或者默认的 /home。
-c,–comment COMMENT。任何字符串,通常是用户全名
-d,–home-dir HOME_DIR 将创建新用户,并使用 HOME_DIR 作为用户登陆目录的值。默认值是将 LOGIN 名附加到 BASE_DIR后面,并使用这作为登陆目录。目录不一定必须已经存在 HOME_DIR,但是会在需要时创建。
-D, --defaults 更改默认值
-g,–gid GROUP 设置用户的主组,GROUP为组名或GID,必须是已经存在的。
-G,–groups GROUP1[,GROUP2,…[,GROUPN]]]用户还属于的附加组列表。每个组都用逗号隔开,没有中间的空格。这里的组受到了 -g 选项给定的组同样的限制。默认上,用户只属于初始组。
-m, --create-home 如果不存在,则创建用户主目录。
usermod:
修改一个已经存在的用户账户。
格式:usermod [选项] 用户名
-a,–append 将用户添加到附加组,只能和-G选项一起使用
-d,–home HOME_DIR 设置用户的主目录。如果加上 -m 选项,当前主目录的内容将会移动到新主目录中,如果不存在,则创建。
-m, --move-home 将用户的主目录移动到新位置。这个选项只有和 -d (或 --home) 选项组合使用时才有效。
-G, --groups GROUP1[,GROUP2,…[,GROUPN]]] 用户还属于的附加组列表。组之间使用逗号分隔,没有空格。这些组需要遵守和 -g 选项中给的组同样的限制。如果用户当前是一个组的成员,而这个组没有列在这里,用户将被从那个组里便删除。这个行为可以通过 -a选项修改,这使用户追加到给出的附加组列表中。
userdel:
删除指定的用户账户和相关文件。
格式:userdel [选项] 用户名
-r, --remove 用户主目录中的文件将随用户主目录和用户邮箱一起删除。在其它文件系统中的文件必须手动搜索并删除。
-h,–help 显示帮助信息。
groupadd:
groupadd命令用于创建一个新的用户组。
命令格式:groupadd [选项] group
-g,–gid GID 指定添加组的gid,GID是一个非负整数并且不能和已有组号重复,默认gid为当前最大gid+1
-r, --system 创建一个系统组。
groupmod:
groupmod用于修改一个已经存在的组的相关信息。
格式:groupmod [选项] GROUP
-g,–gid GID 将原有的gid修改为新的GID
-n,–new-name 将原有的组名修改为新的组名
groupdel:
groupdel删除一个已经存在的组。
格式:groupdel [选项] group

注:给出的组名必须存在。

14文件类型与格式

文件

计算机能在多种不同的物理介质上存储信息,为了方便地使用计算机系统,操作系统一般提供了信息存储的统一逻辑接口。操作系统对存储设备的各种属性加以抽象,从而定义了逻辑存储单元(文件)。文件是记录在外存上的关联信息的集合。
###文件类型
在Linux文件系统中通常有多种文件类型,最常见的是普通文件和目录。除此之外,还有另外五种不同类型的文件。
我们可以使用ls -l命令来查看文件类型。
7种文件类型分别为:
-:普通类型
d:目录
c:字符设备文件
b:块设备文件
s:本地套接字文件
p:命名管道
l:链接文件
示例:

[root@ bash]# ls -l
total 6
-rwxrwxrwx 1 root root 3446 May 10 09:41 bash_test.sh
-rwxrwxrwx 1 root root    0 May 10 10:19 error.txt
-rwxrwxrwx 1 root root   39 May  9 16:30 for.sh
-rwxrwxrwx 1 root root    0 May 10 09:41 hello world.txt
-rwxrwxrwx 1 root root   46 May  9 17:18 libout.sh
-rwxrwxrwx 1 root root  114 May 10 10:15 ls.txt
-rwxrwxrwx 1 root root   28 May 10 10:14 pwd.txt

在上述长格式的输出下,每个文件的第一个字母表示了文件类型。

文件格式

在Windows系统中,采用文件扩展名来标识文件格式,比如1.txt,2.exe,3.doc,4.mp3等。这里的.txt,.exe,.doc,.mp3就是文件扩展名,通常Windows系统会根据文件扩展名选择相应的应用程序处理文件。
而在Linux系统中,并不依据文件扩展名来区分文件格式。实际上,Linux系统中的文件后缀名通常是帮助用户区分不同格式的文件。一个文件具体为什么格式跟其扩展名没有任何关系。可以使用file命令查看文件格式。

file命令的详细使用方法,请参阅man file。

[root@ bash]# file bash_test.sh 
bash_test.sh: UTF-8 Unicode text

修改文件后缀,类型无变化。

15权限管理

权限介绍

Linux是一个支持多用户的系统,为了区分管理不同用户的文件,Linux设置了不同的访问权限区别不同的用户。对一个文件而言,将访问它的用户分为三类:文件拥有者,拥有者同组的成员,其他组的成员。

[root@ bash]# ls -l
total 6
-rwxrwxrwx 1 root root 3446 May 10 09:41 bash_test.sh
-rwxrwxrwx 1 root root    0 May 10 10:19 error.txt
-rwxrwxrwx 1 root root   39 May  9 16:30 for.sh
-rwxrwxrwx 1 root root    0 May 10 09:41 hello world.txt
-rwxrwxrwx 1 root root   46 May  9 17:18 libout.sh
-rwxrwxrwx 1 root root  114 May 10 10:15 ls.txt
-rwxrwxrwx 1 root root   28 May 10 10:14 pwd.txt

之前介绍过长格式中第一个字符表示文件的类型:d表示文件夹、-表示文件、l表示链接文件等。那么后面9个字母即是针对上述三大类用户的访问权限,访问权限分为:可读、可写、可执行或可搜索(对目录而言)(分别用字母r、w、x表示,-表示)。9个字母,每三个一组,分别为读、写、执行的权限设置。
###修改权限chmod
chmod是coreutils中的一个修改文件模式位设置的程序,可以帮助我们设置文件的权限。文件模式位可以采取符号表示或者八进制数字表示,下面分别介绍它的用法。
格式:chmod [ugoa] [±=] [rwx] filename
其中ugoa表示不同类别的用户,u代表文件持有者,g代表持有者同组的用户,o代表其他用户,a代表所有用户。(这些用户类别可以组合使用,若不给出指定用户类别则默认为a,但是这会受到掩码设置的影响)
±= 表示对文件进行何种方式的设置,+表示添加权限,-表示减少权限,=表示设置权限为给定的值。
rwx表示具体权限设置。r表示读取权限,w表示写入权限,x表示执行或搜索的权限。
示例:

chmod u=rwx test.txt   # 给当前用户添加rwx权限
chmod ugo=rwx test.txt # 给ugo用户添加rwx权限

另一种表示权限的方式是八进制数字0~7,其中4表示读权限,2表示写权限,1表示执行或搜索权限(不同权限值可以相加组合)。0表示无权限,1表示执行或搜索权限,2表示写权限,3(1+2)表示写权限和执行或搜索权限,4表示读权限,5(4+1)表示执行或搜索权限和读权限,6表示读写权限,7表示所有权限。这样,就可以用三个八进制数字对文件进行权限管理,不需要再使用ugoa。

chmod =000 test.txt # 给ugo(分别对应三个数字设置的权限)用户设置0权限
chmod 222 test.txt  # 给ugo(分别对应三个数字设置的权限)用户设置写权限
chmod +111 test.txt  # 给ugo再加上执行权限

16管道

之前在输入输出重定向中提到,每个进程默认都有三个对应的文件描述符(stdin、stdout、stderr),可以通过< >来重定向进程的文件描述符。如果要在进程间传递数据的话,使用输入输出重定向就比较麻烦了。比如:

[root@ bash]# ls > ls.txt
[root@ bash]# grep bash < ls.txt
bash_test.sh

匿名管道

管道(pipe)也可以看做是重定向的一种,它的作用是将一个进程的标准输出与另一个进程的标准输入相连接。
格式:command_1 | command_2 | command_3 | … | command_N
对于上面的例子,使用管道:

[root@ bash]#  ls | grep bash
bash_test.sh

通过使用管道,我们可以轻松地将若干个命令组合起来使用。

[root@ bash]#  ls | wc -l
7

注:wc命令可以用于统计文件的行数、单词数和字节数,具体使用方法见man wc。

ls -l | tail
ls -l | tail | sort

命名管道

上面使用的管道叫做匿名管道,还存在着另一种命名管道或者叫FIFO。命名管道是一种特殊类型的文件,主要用于进程间的通信。

# 标签页1
cd ~
mkfifo fifo
echo 1 > fifo # 此时进程会阻塞,再新打开一个标签页

# 标签页2
cd ~
echo 2 > fifo # 再新打开一个标签页

# 标签页3
cat fifo 
# 输出如下,先入先出。
2
1

再次cat fifo会阻塞,因为没有输入。
重新打开一个终端,echo 1 > fifo。
阻塞的cat fifo会打印1。

查看fifo文件:

[root@ ~]# ls -l fifo 
prw-r--r-- 1 root root 0 May 10 13:35 fifo

文件类型为p命名管道。

参考文集:https://www.jianshu.com/nb/32401470
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值