有人能告诉我为什么这不起作用吗?我正在尝试使用文件描述符,但感觉有点迷茫。
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
前三行代码可以正常运行,但后面两行出现了错误。为什么呢?
有人能告诉我为什么这不起作用吗?我正在尝试使用文件描述符,但感觉有点迷茫。
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
前三行代码可以正常运行,但后面两行出现了错误。为什么呢?
文件描述符0,1和2分别对应标准输入(stdin),标准输出(stdout)和标准错误(stderr)。
文件描述符3、4、...、9用于额外的文件。为了使用它们,您需要先打开它们。例如:
exec 3<> /tmp/foo #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.
想了解更多信息,请查看高级Bash脚本指南:第20章I/O重定向。
这是一个老问题,但有一件事需要澄清。
虽然Carl Norum和dogbane的答案是正确的,但前提是更改脚本以使其工作。
我想指出的是您无需更改脚本:
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
如果您以不同的方式调用它,它就可以工作:
./fdtest 3>&1 4>&1
意思是将文件描述符3和4重定向到1(标准输出)。
关键在于,如果父进程提供了除1和2(stdout和stderr)之外的描述符,则该脚本想要写入其他描述符是完全可以的。
你的示例实际上非常有趣,因为此脚本可以写入4个不同的文件:
./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
现在你已经有了4个单独文件的输出:
$ for f in file*; do echo $f:; cat $f; done
file1.txt:
This
file2.txt:
is
file3.txt:
a
file4.txt:
test.
更有趣的是,您的程序无需对这些文件具有写权限,因为它实际上并没有打开它们。
例如,当我运行sudo -s
以将用户更改为root时,在root下创建目录,并尝试以我的常规用户(在我的情况下为rsp)运行以下命令:
# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt'
我遇到了一个错误:
bash: file1.txt: Permission denied
但是,如果我在su
之外进行重定向:
# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
注意单引号的不同。它有效,我得到:
# ls -alp
total 56
drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./
drwxrwxr-x 3 rsp rsp 4096 Jun 23 15:01 ../
-rw-r--r-- 1 root root 5 Jun 23 15:05 file1.txt
-rw-r--r-- 1 root root 39 Jun 23 15:05 file2.txt
-rw-r--r-- 1 root root 2 Jun 23 15:05 file3.txt
-rw-r--r-- 1 root root 6 Jun 23 15:05 file4.txt
尽管脚本没有权限创建这些文件,但在由root拥有的目录中,这些文件仍然归root所有 - 这四个文件都是。
另一个例子是使用chroot jail或容器,在其中运行程序,即使以root身份运行,也无法访问那些文件,并将这些描述符重定向到需要的外部位置,而无需实际授予该脚本对整个文件系统或任何其他内容的访问权限。
关键是你发现了一个非常有趣且有用的机制。你不必像其他答案建议的那样在脚本内部打开所有文件。有时候在脚本调用期间重新定向它们是很有用的。
总之,这就是:
echo "This"
实际上等同于:
echo "This" >&1
并运行程序为:
./program >file.txt
就等同于:
./program 1>file.txt
数字1只是一个默认数字,代表标准输出stdout。
但是即使是下面的这个程序:
#!/bin/bash
echo "This"
可能会产生“坏描述符”错误。如何出现这种情况呢?当以以下方式运行时:
./fdtest2 >&-
输出将为:
./fdtest2: line 2: echo: write error: Bad file descriptor
添加>&-
(与1>&-
相同)表示关闭标准输出。添加2>&-
将意味着关闭stderr。
你甚至可以做更复杂的事情。你原来的脚本:
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
仅运行以下内容时:
./fdtest
打印:
This
is
./fdtest: line 4: 3: Bad file descriptor
./fdtest: line 5: 4: Bad file descriptor
但您可以通过运行以下命令使描述符3和4正常工作,但让第1个失败:
./fdtest 3>&1 4>&1 1>&-
输出结果为:
./fdtest: line 2: echo: write error: Bad file descriptor
is
a
test.
如果您想使描述符1和2都失败,则应像这样运行:
./fdtest 3>&1 4>&1 1>&- 2>&-
你将获得:
a
test.
为什么?什么都没有失败吗? 有失败,但没有stderr(文件描述符号2)你没有看到错误信息!
我认为以这种方式进行实验非常有用,可以让人对描述符及其重定向的工作原理有所了解。
你的脚本确实是一个非常有趣的例子 - 我认为它根本没有问题,只是你使用方法不正确! :)
./fdtest
,那么fdtest
是否有办法确定文件描述符3和4不存在? - MattCochranels -la
显示也是由 root 拥有的。这里没有违反权限的技巧,只是 "将你的重定向放在一个字符串内,以便由用户切换子程序(在你的情况下为 su)执行时将作为该子程序执行的用户进行检查"。从语义上讲,重定向应用于它们声明之前的命令。 - Ajax这是因为这些文件描述符没有指向任何东西!通常的默认文件描述符是标准输入0
,标准输出1
和标准错误流2
。由于您的脚本没有打开其他文件,因此没有其他有效的文件描述符。您可以使用 exec
在bash中打开一个文件。这是您示例的修改版:
#!/bin/bash
exec 3> out1 # open file 'out1' for writing, assign to fd 3
exec 4> out2 # open file 'out2' for writing, assign to fd 4
echo "This" # output to fd 1 (stdout)
echo "is" >&2 # output to fd 2 (stderr)
echo "a" >&3 # output to fd 3
echo "test." >&4 # output to fd 4
现在我们将运行它:
$ ls
script
$ ./script
This
is
$ ls
out1 out2 script
$ cat out*
a
test.
$
正如您所见,额外的输出被发送到请求的文件中。
stdout
或stderr
。你为什么需要或想要使用其他文件呢? - Carl Norum/dev/null
的位置。我经常在脚本中这样做,并利用附加的文件描述符传递返回更多详细信息或响应,而不仅仅是return #
。
#!/bin/bash
2>/dev/null >&3 || exec 3>/dev/null
2>/dev/null >&4 || exec 4>/dev/null
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
/dev/null
,以丢弃可能的bash: #: Bad file descriptor
响应,并使用||
来处理以下命令exec #>/dev/null
,当前一个命令以非零状态退出时执行。如果文件描述符已经打开,则两个测试将返回零状态,exec ...
命令将不会被执行。
没有任何重定向调用脚本产生:
# ./script.sh
This
is
a
和test
的重定向被发送到/dev/null
。# ./script.sh 3>temp.txt 4>>temp.txt
This
is
# cat temp.txt
a
test.
第一个重定向3>temp.txt
会覆盖文件temp.txt
,而4>>temp.txt
则是将内容追加到文件中。
最后,如果你想要将默认文件重定向到脚本内部而不是/dev/null
,或者改变脚本的执行方式并将那些额外的文件描述符重定向到任何你想要的地方,都是可以实现的。
exec 3>&1
会导致echo hi >&3
将“hi”打印到标准输出。 - Felipe Alvarez3<>
用于读取和写入。由于我们只向此示例进行写入,因此在此片段中是正确的使用3>
;但是,通常情况下,我们希望从描述符读取我们写入的内容。 - Yzmir Ramirez