Observe:
$ ls $ ls > list $ cat list list
This appears to indicate that when ls is executed that the redirection into file list has already begun and the list file is already created. A fine enough explanation at any rate, but the question is this: How can I prevent this from happening? What I expected to happen was ls would execute and its output dumped into list and that is what I want.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
As you’ve noticed, the file is created before ls is run. This is due to how the shell handles its order of operations. In order to do
ls > file
the shell needs to create file and then set stdout to point to that and the finally run the ls program.
So you have some options.
- Create the file in another directory (eg
/tmp) and thenmvit to the final directory - Create it as a hidden file (
.file) and rename it - Use
grepto remove the file from the output - Cheat 🙂
The cheat would be something like
x=$(ls) ; printf "%sn" "$x" > file
This causes the output of ls to be held in a variable, and then we write that out.
Method 2
The output file is created by the shell before ls begins. You can get around this by using tee:
ls | tee list
To thoroughly defeat any race condition, there is always
ls | grep -vx 'list' > list
Or if you like that tee displays the results as well:
ls | grep -vx 'list' | tee list
However, as pointed out in comments, things like this often break when filenames contain strange characters. Unix filenames can generally contain any characters except for NUL and /, so parsing the output of ls is extremely difficult:
- Assigning to a shell variable can fail if a filename ends in one or more
n. - Filtering with
grepfails when the search term lies betweenn. - You can separate filenames with
NULinstead ofnusingfind, but it can be difficult to convert this into something resembling the traditional sorted, newline-separated output ofls. - Removing the output filename from the list may be incorrect if it already exists.
So the only truly effective way to do this is to create the output file somewhere else, and move it into place. If you will never use ls -a, then this works:
ls > .list && mv .list list
If you might be using ls -a, then .list could appear in your output but no longer exist in the directory. So then you would use a different directory, like /tmp to store the intermediate result. Of course, if you always use /tmp you run into trouble there, so you can write a script:
#!/bin/sh
OUTDIR='/tmp'
if [ "${PWD}" = '/tmp' ]; then
OUTDIR="${HOME}"
fi
ls > "${OUTDIR}/list" && mv "${OUTDIR}/list" list
This seems overly complicated for the task, though.
But the entire cause of the issue is that the shell is creating the output file before the command begins. We can take that into consideration and just have the shell list the files for us. Then we don’t even need ls at all!
printf '%sn' * > list
This will work until you have too many files in the directory to fit into an argument list.
Method 3
You can make the filename temporarily hidden:
ls >.list && mv .list list
Method 4
You can use moreutils sponge:
ls | sponge list
Or with zsh:
cp =(ls) list
With GNU ls:
ls -I list > list
(though if there had been a file called list before, that means it won’t be listed).
Since ls output is sorted anyway, you can also use (assuming your filenames don’t contain newline characters):
ls | sort -o list
Or to avoid the double sorting, if your ls supports -U for Unsorted (beware some ls implementations have a -U for something else):
ls -U | sort -o list
Method 5
Partial/most credit goes to @StephenHarris…
echo "`ls`" > list
equivalent to
echo "$(ls)" > list
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0