How to make find and printf works in bash script

2019-03-05 12:51发布

My environment: Linux Redhat, Bash 3.5

I have created a bash script to list all of files in sub directories using script below.

#!/bin/bash
for i in $( find . -maxdepth 1 -mindepth 1 -type d ); do
    b=$(echo $i | awk '{print substr($1,3); }' )
    find $i -type f > $i/$b.txt
done

I want to add sha1sum to my script, I've edit it as shown below.

#!/bin/bash
for i in $( find . -maxdepth 1 -mindepth 1 -type d ); do
    b=$(echo $i | awk '{print substr($1,3); }' )
    find $i -type f ! -iname '*thumbs.db*' -printf "%b:" -exec sha1sum -b {} \; > ./$i/$b.txt
done

The second code is failed with error below

No such file or directory

1条回答
男人必须洒脱
2楼-- · 2019-03-05 13:34

Since you don't specify in what way your attempt failed [See note 1, below], I've just provided some thoughts about how to solve the underlying question of producing lists of checksums for each directory.

Your use of find is both unnecessary and unsafe (if there are any directories whose names include whitespace or shell metacharacters.

For example,

$( find . -maxdepth 1 -mindepth 1 -type d )

is roughly the same as the "glob"

./*/

except that the glob does not perform pathname expansion or word-splitting on the individual directories. Furthermore, you could use the glob */, which will avoid the ./ prefix, which you evidently don't want.

Also,

b=$(echo $i | awk '{print substr($1,3); }' )

could be written much more simply as

b=${i:3}   # Bash substring notation

or

b=${i#./}  # Posix standard prefix deletion

But, as noted above, you could use a glob and thereby avoid any having to remove the prefix.

So you could have simply written:

#!/bin/bash
for b in */; do
    find "$b" -type f > "$b/$b.txt"
done

Note the quotes around the variable expansions. If you really need the %b format to print filenames, then you definitely need to quote the expansions to avoid problems with whitespace and shell metacharacters, but anyway you should develop the a habit of quoting variable expansions.

That will produce a slightly different output than your script since it doesn't place the ./ at the beginning of each line in the output file, but if you really wanted that you could use find "./$b" ....

I don't understand why you feel the need to use printf to output the filename for the SHA-1 checksums, because sha1sum already prints the filename. It's true that it does so after the checksum instead of before. There is actually a good reason for that behaviour: since there is no way of knowing what characters the filename might include, putting the checksum first makes it easier to parse the output.

Note that in the output of sha1sum, the output for each file whose name includes a backslash or newline, the checksum line will start with a backslash and the backslash and newline characters in the filename will be escaped. This produces an effect somewhat similar to that of using the %b format to printf.

If you really want to turn the order around, you can do so easily enough with awk:

#!/bin/bash
for b in */; do
    find "$b" -type f '!' -iname '*thumbs.db*' -exec sha1sum {} + |
    awk '{print "$2:$1"}' > "$b/$b.txt"
done

Using + instead of ; to terminate the -exec option lets find execute the command with a list of filenames instead of just a single filename, which is much more efficient.


Notes

  1. See the Stack Overflow help page, from which I quote:

    Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers.

    "It doesn't work" is not a "specific problem or error". You should always include the literal text of the error message or a clear statement of how the result of the program failed to meet your expectations.

查看更多
登录 后发表回答