3>&4-
is a ksh93 extension also supported by bash and that is short for 3>&4 4>&-
, that is 3 now points to where 4 used to, and 4 is now closed, so what was pointed to by 4 has now moved to 3.
Typical usage would be in cases where you've duplicated stdin
or stdout
to save a copy of it and want to restore it, like in:
Suppose you want to capture the stderr of a command (and stderr only) while leaving stdout alone in a variable.
Command substitution var=$(cmd)
, creates a pipe. The writing end of the pipe becomes cmd
's stdout (file descriptor 1) and the other end is read by the shell to fill up the variable.
Now, if you want stderr
to go to the variable, you could do: var=$(cmd 2>&1)
. Now both fd 1 (stdout) and 2 (stderr) go to the pipe (and eventually to the variable), which is only half of what we want.
If we do var=$(cmd 2>&1-)
(short for var=$(cmd 2>&1 >&-
), now only cmd
's stderr goes to the pipe, but fd 1 is closed. If cmd
tries to write any output, that would return with a EBADF
error, if it opens a file, it will get the first free fd and the open file will be assigned it to stdout
unless the command guards against that! Not what we want either.
If we want the stdout of cmd
to be left alone, that is to point to the same resource that it pointed to outside the command substitution, then we need somehow to bring that resource inside the command substitution. For that we can do a copy of stdout
outside the command substitution to take it inside.
{
var=$(cmd)
} 3>&1
Which is a cleaner way to write:
exec 3>&1
var=$(cmd)
exec 3>&-
(which also has the benefit of restoring fd 3 instead of closing it in the end).
Then upon the {
(or the exec 3>&1
) and up to the }
, both fd 1 and 3 point to the same resource fd 1 pointed to initially. fd 3 will also point to that resource inside the command substitution (command substitution only redirects the fd 1, stdout). So above, for cmd
, we've got for fds 1, 2, 3:
- the pipe to var
- untouched
- same as what 1 points to outside the command substitution
If we change it to:
{
var=$(cmd 2>&1 >&3)
} 3>&1-
Then it becomes:
- same as what 1 points to outside the command substitution
- the pipe to var
- same as what 1 points to outside the command substitution
Now, we've got what we wanted: stderr goes to the pipe and stdout is left untouched. However, we're leaking that fd 3 to cmd
.
While commands (by convention) assume fds 0 to 2 to be open and be standard input, output and error, they don't assume anything of other fds. Most likely they will leave that fd 3 untouched. If they need another file descriptor, they'll just do an open()/dup()/socket()...
which will return the first available file descriptor. If (like a shell script that does exec 3>&1
) they need to use that fd
specifically, they will first assign it to something (and in that process, the resource held by our fd 3 will be released by that process).
It's good practice to close that fd 3 since cmd
doesn't make use of it, but it's no big deal if we leave it assigned before we call cmd
. The problems may be: that cmd
(and potentially other processes that it spawns) has one fewer fd available to it. A potentially more serious problem is if the resource that that fd points to may end up held by a process spawned by that cmd
in background. It can be a concern if that resource is a pipe or other inter-process communication channel (like when your script is being run as script_output=$(your-script)
), as that will mean the process reading from the other end will never see end-of-file until that background process terminates.
So here, it's better to write:
{
var=$(cmd 2>&1 >&3 3>&-)
} 3>&1
Which, with bash
can be shorten to:
{
var=$(cmd 2>&1 >&3-)
} 3>&1
To sum up the reasons why it's rarely used:
- it's non-standard and just syntactic sugar. You've got to balance saving a few keystrokes with making your script less portable and less obvious to people not used to that uncommon feature.
- The need to close the original fd after duplicating it is often overlooked because most of the time, we don't suffer from the consequence, so we just do
>&3
instead of >&3-
or >&3 3>&-
.
Proof that it's rarely used, as you found out is that it is bogus in bash. In bash compound-command 3>&4-
or any-builtin 3>&4-
leaves fd 4 closed even after compound-command
or any-builtin
has returned. A patch to fix the issue is now (2013-02-19) available.
The spawn command returns a readable stream, so you will need to pipe that to a writable stream to do something useful.
Piping to a file
// the filed module makes simplifies working with the filesystem
var filed = require('filed')
var path = require('path')
var spawn = require('spawn')
var outputPath = path.join(__dirname, 'out.txt')
// filed is smart enough to create a writable stream that we can pipe to
var writableStream = filed(outputPath)
var cmd = path.join(__dirname, 'my_script.bash')
var args = [] // you can option pass arguments to your spawned process
var child = spawn(cmd, args)
// child.stdout and child.stderr are both streams so they will emit data events
// streams can be piped to other streams
child.stdout.pipe(writableStream)
child.stderr.pipe(writableStream)
child.on('error', function (err) {
console.log('an error occurred')
console.dir(err)
})
// code will be the exit code of your spawned process. 0 on success, a positive integer on error
child.on('close', function (code) {
if (code !== 0) {
console.dir('spawned process exited with error code', code)
return
}
console.dir('spawned process completed correctly at wrote to file at path', outputPath)
})
You will need to install the filed module to run the example above
npm install filed
Piping to stdout and stderr
process.stdout and process.stderr are both writable streams so you can pipe the output of your spawned command directly to the console as well
var path = require('path')
var spawn = require('spawn')
var cmd = path.join(__dirname, 'my_script.bash')
var args = [] // you can option pass arguments to your spawned process
var child = spawn(cmd, args)
// child is a stream so it will emit events
child.stderr.pipe(process.stderr)
child.stdout.pipe(process.stderr)
child.on('error', function (err) {
console.log('an error occurred')
console.dir(err)
})
// code will be the exit code of your spawned process. 0 on success, a positive integer on error
child.on('close', function (code) {
if (code !== 0) {
console.dir('spawned process exited with error code', code)
return
}
console.dir('spawned process completed correctly at wrote to file at path', outputPath)
})
Best Answer
zsh
does not support moving file descriptor syntax likebash
orksh
. So1>&3-
means redirect both standard output and standard error to file named3-
:For achieving the same moving file descriptor behavior like
bash
andksh
, in any POSIX shell:That's what
1>&3-
inbash
andksh
does internally, duplicated file descriptor 3 to file descriptor 1 then close file descriptor 3.