Abstract:
1) Linux Shell 命令的标准输入、标准输出、标准错误,及其重定位;
2)Linux Shell 操作自定义文件描述符;
文件描述符是与文件相关联的一些整数,他们保持与已打开文件的关联。众所周知的文件描述符是标准输入stdin、标准输出stdout、标准错误stderr,我们可以重定位这些文件描述符关联文件的内容到另外一个文件文件描述符。
1. Linux Shell 命令的标准输入、标准输出、标准错误
当我们在编写 shell 脚本时,我们会非常频繁地操作执行命令的标准输入stdin、标准输出stdout、标准错误stderr。过滤 shell 脚本或者执行命令的输出信息并且把它们重定位到特定的地方,成了我们分析脚本执行情况的必要工作。当我们执行脚本文件或者执行一个 shell 命令的时候,单从终端输出我们很难区分哪些是标准输出,哪些是标准错误。然而,我们把这些信息重定向特定的地方,以便于我们分析脚本文件及 shell 命令的执行情况。
文件描述符是与打开文件或者数据流相关联的整数,0、1、2 是系统保留的三个文件描述符,分别对应标准输入、标准输出、标准错误。
Linux Shell 使用 " > " ">>" 进行对文件描述符进行重定位。
">" 与 ">>" 的作用是不一样的,前者使用本次输出内容替换原有文件的内容,后者则是把本次输出追加到原文件的后面。
在对文件描述符进行详细分析之前,先执行以下命令,为后面的实验操作准备一些必要的文件:
- echo -e "\e[42;31m --- Prepare resource file ---\e[0m";
- echo "This is example text 1." > temp.txt;
- echo "This is example text 2." >> temp.txt;
- cat temp.txt;
- echo "a1" > a1;
- echo "a2" > a2;
- echo "a3" > a3;
- sudo chmod 000 a3;
- ls -alF a*;
1.1 获取 Linux Shell 命令执行结果
Linux Shell 中每一条 shell 命令的执行都有返回值,用以返回命令的执行结果。
在命令执行结束后,通过引用 $? 获取最后一条命令执行的返回值,当返回值为零代表命令执行成功。
- echo -e "\e[42;31m --- Get command execution result! ---\e[0m";
- ls + ;
- echo -e "\e[1;32m --- 'ls + ' executed and return value is: $? \e[0m";
- ls -alF ./ ;
- echo -e "\e[1;32m --- 'ls -alF ./' executed and return value is: $? \e[0m";
由于 “ ls + ” 的输入参数 “ + ”是一个 “ls” 命令不能接受的参数,所以由上面的执行结果可以看到命令执行的错误信息以及命令执行的返回值为: 2 ;
接下来的命令 “ls -alF” 是一条合法的命令,所以,我们看到它成功执行的返回值为:0 ;
重定位运算符 ">" ">>" 的默认参数为标准输出 stdout ,即 1 ;
所以 ">" 等价于 "1>"; ">>" 等价于 "1>>"
1.2 Linux Shell 标准输出 stdout 的重定位
- echo -e "\e[42;31m --- Redirect stdout ! ---\e[0m";
- ls + > out.txt;
- echo -e "\e[1;32m --- 'ls + > out.txt' executed and return value is: $? \e[0m";
- echo -e "\e[1;32m --- Contents of out.txt as following descriptions:\e[0m \e[1;33m";
- cat out.txt;
- echo -e "\e[0m";
- rm out.txt;
由实验结果可以看出标准输出 stdout 重定向输出文件 out.txt 的内容为空;
因为 “ ls + ”命令的输入参数 “+” 为非法参数,所以命令执行失败,没有输出结果,并且同时看到命令执行返回值为: 2 ,非零,表示执行失败。1.3 Linux Shell 标准错误 stderr 的重定位
- echo -e "\e[42;31m --- Redirect stderr ! --- \e[0m";
- ls + 2> out.txt;
- echo -e "\e[1;32m 'ls + 2> out.txt' executed and return value is: $? \e[0m";
- echo -e "\e[1;32m Contents of out.txt as following descriptions:\e[0m \e[1;33m";
- cat out.txt;
- echo -e "\e[0m";
- rm out.txt;
由实验结果看出非法命令 "ls + 2> out.txt" 的执行并没有任何的输出,与之前的姐果一样;
不同的是,错误信息也没有在命令执行的时候输出到终端上面,而是被重定向到了文件 out.txt 里面;1.4 Linux Shell 分别重定向标准输出 stdin 与标准错误 stderr 到指定的文件:
- echo -e "\e[42;31m --- Redirect stdout and stderr to exclusive files ! ---\e[0m";
- cat a* 2>stderr.txt 1>stdout.txt;
- echo -e "\e[1;32m'cat a* 2>stderr.txt 1>stdout.txt' executed and return value is: $? .\e[0m";
- echo -e "\e[1;32mContents of stderr.txt is:\e[0m \e[1;33m";
- cat stderr.txt;
- echo -e "\e[0m";
- echo -e "\e[1;32mContents of stdout.txt is:\e[0m \e[1;33m";
- cat stdout.txt;
- echo -e "\e[0m";
- rm stderr.txt stdout.txt;
这次的执行中无论是标准输出 stdout 与标准错误 stderr 都没有在命令执行时输出到终端上,只是分别输出到 stdout.txt 与 stderr.txt 中;
由于 a3 是一个没有读、写、执行命令权限的文件,所以当 cat a3 的时候就会有错误信息输出到 stderr.txt ;
a1、a2 则把文件内容输出到 stdout.txt ;
1.5 Linux Shell 重定向标准错误到标准输出:
1.5.1 使用 " 2>&1" 把标准错误 stderr 重定向到标准输出 stdout ;
- echo -e "\e[42;31m --- Redirect stderr to stdout approach first! ---\e[0m";
- cat a* > output.txt 2>&1;
- echo -e "\e[1;32m'cat a* 2>&1 output.txt' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of output.txt is:\e[0m \e[1;33m";
- cat output.txt;
- echo -e "\e[0m";
- rm output.txt;
由上面的实验结果看出, 无论是标准输出 stdin 还是标准错误 stderr ,都被重定向到了 output.txt 里面。
1.5.2 使用 "&>" 把标准错误 stderr 重定向到标准输出 stdout ;
- echo -e "\e[42;31m --- Redirect stderr to stdout approach second ! ---\e[0m";
- cat a* &> output.txt;
- echo -e "\e[1;32m'cat a* &> output.txt' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of output.txt is:\e[0m \e[1;33m";
- cat output.txt;
- echo -e "\e[0m";
- rm output.txt
由实验结果,标准错误 stderr 被转化为标准输出 stdout ,并重定向到了 output.txt 中;
1.6 把标准错误 stderr 重定向到 /dev/null
- echo -e "\e[42;31m --- Redirect stderr to /dev/null ! ---\e[0m";
- cat a* 2>/dev/null;
- echo -e "\e[1;32m'cat a* 2>/dev/null' executed and return value is $?\e[0m";
"/dev/null" 是一个特殊的设备文件,所有输入到其中的比特流都会被丢弃;
由实验结果看出,标准输出如常输出到终端上,而标准错误则没有输出,被重定位到 "/dev/null" 。
1.7 使用 tee 命令,实现重定向与终端双输出
当我们使用重定向输出时,所有信息都被重定向输出到我们指定的文件描述符,终端上面就再也看不到这些信息了;
我们可以通过 tee 命令,实现把信息重定向输出到文件的同时输出到终端,附上 tee 的使用说明:
tee 命令会从标准输出接受信息,把信息保存到指定的文件同时,把信息输出到标准输出;
1.7.1 把标准输出 stdout 重定向到 tee 的标准输入 stdin :
- echo -e "\e[42;31m --- Redirect stdout as stdin of tee! --- \e[0m";
- cat a* | tee out.txt | cat -n;
- echo -e "\e[1;32m'cat a* | tee out.txt | cat -n' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of out.txt is:\e[0m \e[1;33m";
- cat out.txt;
- echo -e "\e[0m";
- rm out.txt;
"cat a*" 的标准输出 stdout 通过pipe( "|" )重定向为 tee 的标准输入;
tee 把这些信息重定向到 out.txt 的同时,输出到 tee 的标准输出 stdout ;
tee 的标准输出通过 pipe("|") 重定向到了 "cat -n" 的标准输入;
所以以上实验结果应该这样看:
1)因为pipe("|") 只处理标准输出的信息,所以 cat a3 的错误信息依旧在终端直接输出,没有被重定向处理;
2)tee 从它的标准输入 stdin 只接收到 a1、a2 的内容,所以我们通过 "cat -n" 给输出信息加上行号来标识出 tee 接收到的信息;
3)再通过查看 out.txt 文件,发现 tee 命令输出到 out.txt 的信息,与 "cat -n"的信息是一致的;
1.7.2 把标准错误 stderr 转换成标准输出 stdout ,一同输出到 tee 的标准输入,以便把所有信息重定向到文件的同时,输出到终端:
- echo -e "\e[42;31m --- Redirect stdout burden with stderr as stdin of tee! --- \e[0m";
- cat a* 2>&1 | tee out.txt | cat -n;
- echo -e "\e[1;32m'cat a* 2>&1 | tee out.txt | cat -n' executed and return value is: $?\e[0m"
- echo -e "\e[1;32mContents of out.txt is:\e[0m \e[1;33m";
- cat out.txt;
- echo -e "\e[0m";
- cat a* 2>&1 | tee -a out.txt | cat -n;
- echo -e "\e[1;32m'cat a* 2>&1 | tee -a out.txt | cat -n' executed and return value is: $?\e[0m"
- echo -e "\e[1;32mContents of out.txt is:\e[0m \e[1;33m";
- cat out.txt;
- echo -e "\e[0m";
- rm out.txt;
前面提到的 " 2>&1 " 参数可以把命令的标准错误转换为标准输出,这里利用一下,举个实用的例子,呵呵!
tee 命令会把输出文件之前的内容冲掉,再把自己的输出信息输出到文件,但可以使用 " -a " 参数,实现追加方式把信息重定向到指定文件。
1.8 重定位标准输入 stdin
1.8.1 把重定向的标准输入作为命令的输入参数:
先来看看我们貌似已经十分熟悉的 cat 命令的帮助,不知道各位是否留意到有这样一招,反正我之前是没有留意到,呵呵!!!
原来 cat 使用 "-" 作为参数,就是把它接受的标准输入stdin 直接输出到它的标准输出;
呵呵!只能说之前学艺不精,没看到啊!
这里也学会了一点,就是在看linux 下命令的 help 时,我们要多加留心一下是否接受 stdin ;
如果接受 stdin 作为输入,这些命令的扩展用法就可以多好多咯。。。
好,言归正传,来一段实践代码再说,有图有真相嘛,呵呵!!!
- echo -e "\e[42;31m --- Use stdin as a command argument ! --- \e[0m";
- ls -alF | cat;
- echo -e "\e[1;32m'ls -alF | cat' executed and return value is: $?\e[0m"
- ls -alF | cat -;
- echo -e "\e[1;32m'ls -alF | cat -' executed and return value is: $?\e[0m";
- ls -alF | cat -n;
- echo -e "\e[1;32m'ls -alF | cat -n' executed and return value is: $?\e[0m";
- ls -alF | cat /dev/stdin;
- echo -e "\e[1;32m'ls -alF | cat /dev/stdin' executed and return value is: $?\e[0m"
这里要提提的就是 "-" 等价于 /dev/stdin ,你懂的。。。
1.8.2 从文件重定向 shell 命令的标准输入:
Linux Shell 使用 "cmd < FILE " 的方式从一个文件重定向命令的标准输入;
- echo -e "\e[42;31m --- Redirect stdin from file ! --- \e[0m";
- cat < a1
- echo -e "\e[1;32m'cat < a1' executed and return value is: $?\e[0m";
- cat - < a2;
- echo -e "\e[1;32m'cat - < a2 executed and return value is: $?\e[0m";
- cat /dev/stdin < temp.txt;
- echo -e "\e[1;32m'cat /dev/stdin < temp.txt' executed and return value is: $?\e[0m";
上面的 3 条执行命令把 a1、a2、a3 三个文件的内容重定向到命令的标准输入 stdin 。
1.8.3 把文本块重定向为 shell 命令的标准输入 stdin :
有时候,我们需要把一个文本块(多行的文本)重定向给一个 shell 命令作为标准输入。
我们假设有这样一种情况,文本块来源于一个 shell 执行脚本里面,在这个 shell 脚本执行的时候,需要把这个文本块输出到一个文件;
如这样一个例子,一个 shell 脚本需要把它的执行 log 写到一个文件里面,
在文件的开头,需要加入几行信息作为这个 log 文件的开头用以注释一下这个文件,我们可以如下操作:
- echo -e "\e[42;31m --- Redirecting from a text block to a file within a script! --- \e[0m";
- cat <<EOF>log.txt
- Hello
- EOF
- echo -e "\e[1;32m'cat < log.txt Hello world! EOF' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of log.txt as following: \e[0m \e[1;33m";
- cat log.txt;
- echo -e "\e[0m";
- echo -e "\e[42;31m --- Redirecting from a text block to append into a file within a script! --- \e[0m";
- cat <<EOF >> log.txt
- Nice to meet
- EOF
- echo -e "\e[1;32m'cat <> log.txt Nice to meet you! EOF' executed and return value is $?\e[0m";
- echo -e "\e[1;32mContents of log.txt as following: \e[0m \e[1;33m";
- cat log.txt;
- echo -e "\e[0m";
- rm log.txt;
1)使用 "<text block EOF" 的形式把一个 text block 重定向到一个 shell command 的标准输入;
2)上述实验把 text block 重定向给 cat 命令的标准输入, cat 再把它自己的标准输出 stdout 重定向到 log.txt ;
3)上述实验采取了 "> log.txt" 与 ">> log.txt" 两种方式对 cat 的标准输出 stdout 进行重定向到 log.txt ,
前者是重新创建文件进行信息的输出,后者是以追加的方式进行重定向输出;
2. 用户自定义文件描述符:
现在我们知道 0、1、2是 shell 保留的文件描述符;
另外,我们还可以定义自己的文件描述符,并对其进行读写;
用户自定义文件描述符使用 "exec" 命令进行创建;
用户自定义文件描述符的创建有三种形式:
1)只读方式的文件描述符;
2)截断模式创建写方式的文件描述符;
3)追加模式创建写方式的文件描述符;
2.1 以读方式创建文件描述符:
使用 " exec descriptor<file Name " 的模式创建只读模式的用户自定义文件描述符;
使用 "&descriptor" 的模式引用文件描述符;
只读方式的文件描述符只能读取一次,要再次读取,需要对文件描述符重新打开赋值;
- echo -e "\e[42;31m --- Create a file descriptor for reading file! --- \e[0m";
- exec 3<a1;
- echo -e "\e[1;32m'exec 3<a1' executed="" to="" create="" a="" file="" descriptor="" 3="" for="" reading="" and="" return="" value="" is:="" $?="" ---\e[0m"<="" span="">;
- echo -e "\e[1;32mContents of a1 as following output:\e[0m \e[1;33m";
- cat <&3;
- echo -e "\e[0m";
- echo -e "\e[1;32mContents of a1 with file descriptor 3 to read again as following output:\e[0m \e[1;33m";
- cat <&3;
- echo -e "\e[0m";
如实验结果,当我们再次打开文件对文件描述符重新赋值之前,我们再次使用文件描述符 3 对文件进行读取时,没有任何输出内容。
2.2 以截断模式创建写方式的文件描述符:
所谓截断模式,就是相当于重新创建文件,文件之前的内容将会丢失;
使用 " exec descriptor>file Name " 的模式创建截断模式的用户自定义的写方式的文件描述符;
使用 "&descriptor" 的模式引用文件描述符;
写方式的文件描述符只要一次打开便可多次写入,并且后续的写入操作会一直追加到文件的结尾,但是文件打开之前的内容就会丢失;
- echo -e "\e[42;31m --- Create a file descriptor for writing file in truncation mode!\e[0m";
- echo -e "\e[1;32mContents of a2 before writing it as following output: \e[0m \e[1;33m";
- cat a2;
- echo -e "\e[0m";
- exec 4>a2;
- echo -e "\e[1;32m 'exec 4>a2' executed to create a file descriptor 4 for writing.\e[0m";
- echo "newline in truncation mode" >&4;
- echo -e "\e[1;32m 'echo \"newline in truncation mode\" >&4' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of a2 after writing in truncation mode as follwing output: \e[0m \e[1;33m";
- cat a2;
- echo -e "\e[0m";
- echo "newline in truncation mode 2" >&4;
- echo -e "\e[1;32m 'echo \"newline in truncation mode 2\" >&4' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of a2 after writing twice in truncation mode as follwing output: \e[0m \e[1;33m";
- cat a2;
- echo -e "\e[0m";
- exec 4>a2;
- echo -e "\e[1;32m 'exec 4>a2' executed to reassign file descriptor 4 for writing\e[0m";
- echo "newline in truncation mode 3" >&4;
- echo -e "\e[1;32m 'echo \"newline in truncation mode 3\" >&4' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of a2 after reassign its file descriptor for writing in truncation mode: \e[0m \e[1;33m";
- cat a2;
- echo -e "\e[0m";
由上述实验结果可以看到,我们引用 "&4" 文件描述符,并且对其进行多次写入,后面的写入内容会追加到之前写入信息的后面;
当我们对文件描述符进行再次打开后,再引用 "&4" 文件描述符,并且对其进行写入,发现之前两次写入的内容已经丢失,只剩下最后一次写入的信息了。
2.3 以追加模式创建写方式的文件描述符:
所谓追加模式,就是打开文件进行写操作,文件之前的内容不会丢失,写操作写入的内容以追加方式添加到文件的末尾;
使用 " exec descriptor>>file Name " 的模式创建追加模式的用户自定义的写方式的文件描述符;
使用 "&descriptor" 的模式引用文件描述符;
写方式的文件描述符只要一次打开便可多次写入,并且后续的写入操作会一直追加到文件的结尾;
- echo -e "\e[42;31m --- Create a file descriptor for writing file in append mode!\e[0m";
- echo -e "\e[1;32mContents of temp.txt before writing it as following output: \e[0m \e[1;33m";
- cat temp.txt;
- echo -e "\e[0m";
- exec 5>>temp.txt;
- echo -e "\e[1;32m 'exec 5>>temp.txt' executed to create a file descriptor 5 for appending.\e[0m";
- echo "newline in append mode" >&5;
- echo -e "\e[1;32m 'echo \"newline in append mode\" >&5' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of temp.txt after writing in append mode as follwing output: \e[0m \e[1;33m";
- cat temp.txt;
- echo -e "\e[0m";
- echo "newline in append mode 2" >&5;
- echo -e "\e[1;32m 'echo \"newline in append mode 2\" >&5' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of temp.txt after writing twice in append mode as follwing output: \e[0m \e[1;33m";
- cat temp.txt;
- echo -e "\e[0m";
- exec 5>>temp.txt;
- echo -e "\e[1;32m 'exec 5>>temp.txt' executed to reassign file descriptor 5 for writing: $?\e[0m";
- echo "newline in append mode 3" >&5;
- echo -e "\e[1;32m 'echo \"newline in append mode 3\" >&5' executed and return value is: $?\e[0m";
- echo -e "\e[1;32mContents of temp.txt after reassign its file descriptor for writing in append mode: \e[0m \e[1;33m";
- cat temp.txt;
- echo -e "\e[0m";