1

Sory I'm new to shellscripting. I have a variable that contains an option related to find command.

TYPE=("-type f")
NAME=("-name \"*log*\"")

I try put those variable that contains of option to related code.

  • This works:
    LST_FILE=$(find ${2} ${TYPE} -mmin +${HOUR_TO_MIN})
    
    and produces (sample output):
    viv/clean/prototype/asdadsadsa.log
    viv/clean/prototype/logo
    viv/clean/prototype/sadsadasdas.log.gz
    viv/clean/prototype/prototype/log
    viv/clean/prototype/prototype/fewfasfs
    viv/clean/prototype/prototype/og
    
  • But when I try to add the ${NAME} it fail to catch keyword name for searching file that I desired, it got no result.
    LST_FILE=$(find ${2} ${TYPE} ${NAME} -mmin +${HOUR_TO_MIN})
    
    (produces 0 results)

Seems the problem related to wildcard, special character, or something like that.. Really appreciate regarding your suggestions for related case.

AdminBee
  • 22,803

2 Answers2

1

There are two problems here. The first problem is that you're defining the two variables as arrays with only one element, and then using them in the find command as simple, scalar variables. bash obligingly returns the first element of the array for each when you do that.

The second, as C.Aknesil points out, is that bash does not remove double-quotes after expanding a variable. In truth, this is not actually a problem, the problem is that you didn't know it or why bash behaves like this - "it's a feature, not a bug".

Try it like this:

TYPE=(-type f)
NAME=(-name '*log*')

'*log*' needs to be quoted here so that bash doesn't expand the glob and add all matching files in the current directory to the array.

Then, when you need to use them with find:

find "$2" "${TYPE[@]}" -mmin "+$HOUR_TO_MIN"

or

find "$2" "${TYPE[@]}" "${NAME[@]}" -mmin "+$HOUR_TO_MIN"

BTW, note that both $2 and $HOUR_TO_MIN should be double-quoted here, but curly braces {} are not needed because there is no need to disambiguate them from surrounding characters. e.g. $2 does not need {}, but ${2}3 would need them because it would be interpreted as $23 (and not as "$2 followed by a 3") by the shell without them.

$HOUR_TO_MIN is probably OK to not quote (because it's probably defined in the script and probably doesn't have problem characters like spaces etc in it) but it doesn't hurt to quote it and "always quote your variables (except when you KNOW with absolutely certainty that you don't want to, and WHY)" is a good habit to get into.

$2, however, is provided by the user as an argument to the script and could have anything in it, and should always be quoted.


Note: it's not a good idea to do something like LST_FILES=$(find ...) because filenames can have all sorts of annoying characters in them, from whitespace like spaces, tabs, and newlines to shell meta-characters like ;, &, >, and |. The only character that can not be in a filename is a NUL, which means that NUL is the only reliable filename separator that will work with any filename.

Instead, if you need the output of find in a variable, use an array and tell find to use NUL as the filename separator with -print0. e.g.

mapfile -d '' LST_FILES < <(find "$2" "${TYPE[@]}" "${NAME[@]}" -mmin "+$HOUR_TO_MIN" -print0)

The mapfile command (also known as readarray) reads from stdin and uses it to populate an array. In this case, we're telling to use NUL as the delimiter with -d ''.

Try the following as an experiment to see how it works for yourself:

$ mkdir junk
$ cd junk
$ touch foo bar baz 'file with spaces' $'file\nwith\nLFs'
$ ls
bar  baz  file?with?LFs  file with spaces  foo

$ mapfile -d '' LST_FILES < <(find . -type f -print0)

$ declare -p LST_FILES declare -a LST_FILES=([0]="./foo" [1]=$'./file\nwith\nLFs' [2]="./baz" [3]="./file with spaces" [4]="./bar")

$ echo "${#LST_FILES[@]}" # use ${#...} to count elements in an array 5

$ for f in "${LST_FILES[@]}"; do echo "$f" ; done ./foo ./file with LFs ./baz ./file with spaces ./bar

cas
  • 78,579
  • hi, it works like a champ using

    LST_FILE=$(find "$2" "${TYPE[@]}" "${NAME[@]}" -mmin "+$HOUR_TO_MIN")

    but what if I want to add remove command to the LST_FILE, is it correct ? LST_FILE+=(-exec rm -f {} ;) seems fail to remove..

    – user3679987 Aug 14 '21 at 06:40
  • it won't work like a champ with that command substitution. that will break on any filenames that contain whitespace. that's why I said you should use an array to capture the output of find. – cas Aug 14 '21 at 12:13
  • btw, you don't need to use -exec rm .... Use -delete instead. – cas Aug 14 '21 at 12:14
  • ohh ok realy appreciate for your suggestion, thanks.. – user3679987 Aug 14 '21 at 14:09
0

My conclusion is Bash does not perform quote removal for the individual words after word splitting. Hence, you are providing the pattern "*log*" with the double quotes included in the pattern. In other words, you are searching files whose names start and end with a double-quote.

From the Bash manual:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion. [...] After these expansions are performed, quote characters present in the original word are removed unless they have been quoted themselves (quote removal).

You can observe this behavior with the following code:

$ NAME=("-name \"*log*\"")
$ echo $NAME
-name "*log*"

As you see $NAME is provided to echo the same way you provide it to find. The fact that echo prints the double-quotes shows that quotes around the pattern are not removed before calling echo.

As a fix, I suggest using the pattern in find without a variable. To achieve a similar code decomposition, you can create a function where you use find, and call that function afterwords:

function find_files {
  find ... <pattern>
}

LST_FILE=$(find_files)

I could not find a solution where you can store the pattern into a variable. I hope the solution above will be enough.

  • hi appreciate for your suggestion, i need to store the command to variable because i need it to easy for mantain and change for related find command, there's lot of case that need to perform different find command in my code.. – user3679987 Aug 14 '21 at 07:38