You should never use the output of ls
as input to another program. It doesn't work (for any definition of "work" that includes the concepts of reliability or safety), and there is never any need to do so because the shell's globbing (i.e. wildcard expansion) does the job you are trying to do by mis-using ls
. See Why not parse ls
(and what to do instead)?.
If you must do something similar, use find ... -exec {} +
or find ... -print0 | xargs -0r ...
rather than ls
- either of them will actually work, and work safely no matter what characters are in the filenames.
Your for
loop should be written as:
for file in *; do
...
done
You don't even need a for
loop unless you are going to do something else with each $file
within the loop.
Here are some alternative ways to do what your for loop does, by storing the output of md5sum
into an array called hashes
, and then printing it:
using printf
:
$ hashes=( $( md5sum * ) )
$ printf '%s %s\n' "${hashes[@]}" > hashes.txt
$ cat hashes.txt
b026324c6904b2a9cb4b88d6d61c81d1 file.1
26ab0db90d72e28ad0ba1e22ee510510 file.2
6d7fce9fee471194aa8b5b6e47267f03 file.3
Note that there are two %s
separated by two spaces in the printf
format string. This will produce the same output as md5sum
itself.
BTW, you can see how the entries are stored in the array with typeset -p
:
$ typeset -p hashes
declare -a hashes=([0]="b026324c6904b2a9cb4b88d6d61c81d1" [1]="file.1"
[2]="26ab0db90d72e28ad0ba1e22ee510510" [3]="file.2"
[4]="6d7fce9fee471194aa8b5b6e47267f03" [5]="file.3")
The shell has split the output of md5sum
into words (as defined by the value of the shell's $IFS
variable) and put each "word" in a separate element of the array.
using tee
:
$ hashes=( $( md5sum * | tee hashes.txt) )
$ cat hashes.txt
b026324c6904b2a9cb4b88d6d61c81d1 file.1
26ab0db90d72e28ad0ba1e22ee510510 file.2
6d7fce9fee471194aa8b5b6e47267f03 file.3
Knowing this, Lasse Kliemann's trap answer could be written as:
trap ctrl_c INT
ctrl_c() {
printf '%s %s\n' "${hashes[@]}" > hashes.txt
exit 0
}
However, you don't even need a trap here. md5sum
with tee
will do what you want - i.e. simultaneously populate $hashes
AND the hashes.txt
file.
Or, if you don't want to use an array:
$ for file in *; do
hashes+="$(md5sum "$file" | tee -a hashes.txt)"$'\n'
done
$ echo "$hashes"
b026324c6904b2a9cb4b88d6d61c81d1 file.1
26ab0db90d72e28ad0ba1e22ee510510 file.2
6d7fce9fee471194aa8b5b6e47267f03 file.3
Here we're using tee
's -a
(--append
) option because we're running md5sum
in a loop, and don't want each iteration to overwrite the hashes.txt
file.
Note: This will always have an extra blank line at the end of the $hashes
string, increasing the line count by 1. This will not be the case when using an array.
md5sum
does always line buffer its output, so simplymd5sum f1 f2 f3 huge-f4 > list
will leave you with the sums off1
,f2
andf3
inlist
if you press ^C while it's processinghuge-f4
. – Sep 15 '19 at 14:48