I am looking for a graceful way to have bash write both the command I executed, followed by the output of that command to a file from an interactive prompt. Such that running a command like this:

$ls -alh > list_dir

would write something resembling this to list_dir:

$ls -alh > list_dir
total 12K
drwxr-xr-x 2 root root 4.0K Dec 21 13:30 .
drwx------ 5 root root 4.0K Dec 21 13:30 ..
-rw-r--r-- 1 root root  842 Dec 21 13:09 file
-rw-r--r-- 1 root root    0 Dec 21 13:29 file1
-rw-r--r-- 1 root root    0 Dec 21 13:29 file2

I have found many articles with solutions that claim to work within a script, but nothing that has worked from an interactive shell. For example, many people suggest adding this toward the top of a script:

set -x

If I enter this in my shell, the commands are printed (although not nicely) to standard output, but I have not managed to figure out how to include them in output redirection.

I also saw many people suggest using script to do this. This is the closest I've come to finding what I'm looking for, but I don't always want EVERYTHING to be recorded. I want to be in control of which commands are redirected to a file.

Does anyone know of a good solution to this? I'm open to more out-of-the-box ideas so long as they're not too clunky for regular use.

  • Do you require the redirection to be part of the command listed in the file? For instance, in your example, would having ls -ahl instead of ls -ahl > list_dir as the first line of the file be acceptable? – Paul H. Dec 21 '16 at 20:03
  • Would you take an expect script which does the same as script except it allows you to type something like ~l to turn logging on and off? – icarus Dec 22 '16 at 3:58

set -x option puts your script in debugging mode. Where actual command is echoed to stderr. So you need to redirect both stdout and stderr to somefile. try this
your_script.sh &> outputfile.txt

|improve this answer|||||

The xtrace output is going to stderr before the command is evaluated including before the redirections are performed.

So in:

set -x # or set -o xtrace
echo test 2> file

The + echo test goes to wherever stderr was going to, then the shell opens file on fd 2 and then runs echo test. If you want the + echo test to go to file, you'd need:

{ echo test; } 2> file

This time, the redirection is performed for the command group and then the echo test command is evaluated (and the + echo test written to stderr which at that point goes to file).

Note that some shells like AT&T ksh will also output a + 2> file. It's also bogus in some versions of mksh.

That also means that the stderr of any command run within that command group will go the file. To work around that, you'd need:

set -x
  cmd 2>&3 3>&-
} 3>&2 2> file

That is keep a copy of the original stderr (on fd 3) and restore it for the commands inside the command group.

With bash, an alternative is to use the $BASH_XTRACEFD special variable:

exec 7> file

set -x
echo test

If you want a nice xtrace output, try zsh. Also note that it can be customized with the $PS4 special variable.

|improve this answer|||||

I think the simplest solution would be to add a function to your .bashrc file such as this:

my_run(){ echo $@ eval $@ }

Be sure to source .bashrc after adding the function. Then, using your example, from the command line type:

my_run ls -ahl >list_dir.

If you also want to see the output on the screen use this instead:

my_run ls -ahl | tee list_dir

If you don't want to use a function, here are a few ways to do this from the command line:

echo "ls -ahl" >list_dir; eval $@ >>list_dir

If you also want to see the output on the screen, either one of these will work.

echo "ls -ahl" >list_dir; eval $@ | tee -a list_dir

echo "ls -ahl" | tee list_dir | bash >>list_dir

|improve this answer|||||

Not the answer you're looking for? Browse other questions tagged or ask your own question.