I know there are rules for quoting parameters, but no matter what I try, I can't seem to figure out how to make this work.I have a function that takes a single filename as a parameter, then uses that filename to build a command line and runs the command line. I am able to make it work when there are no spaces in any of the files, but when a filename with a space is introduced, it breaks. Everything I try to make it work with spaces either doesn't work or causes it to not work on the files without spaces too.Here is an example script that works with no spaces:#!/bin/bashlist_files() {    file=${1}    cmd=cat ${file}    echo Running command: '${cmd}'    ${cmd}}TARGET_DIR=${1}while IFS= read -d $'\0' -r file; do    list_files ${file}done < <(find ${TARET_DIR} -type f -print 0)I just created a directory structure as follows:./files/|-- one                # ==> Contains the text files/one|-- two                # ==> Contains the text files/two|-- three and four     # ==> Contains the text files/three and four|-- five               # ==> Contains the text files/fiveWhen I run the above script on the directory above, I get the following output:bash-3.2$ ./test.bash ./filesRunning Command: 'cat ./files/one'files/oneRunning Command: 'cat ./files/two'files/twoRunning Command: 'cat ./files/three and four'cat: ./files/three: No such file or directorycat: and: No such file or directorycat: four: No such file or directoryRunning Command: 'cat ./files/five'files/fiveWhat am I doing wrong and how can I make a script like this work no matter what the file names are?NOTE: For this example it would be simple enough to not store the command in a variable first and just print it then call it by typing the command itself twice, but for my real script, the command is much more complex and I would like to only have to maintain it in one place if it should need to change in the future.
Bash Function Parameters - Spaces in Filename
bash;shell;quoting
Instead of:cmd=cat ${file}printf '%s\n' Running command: '${cmd}'${cmd}Either pass a fixed command to eval for it to be interpreted as shell code.cmd='cat -- $file'printf '%s\n' Running command: '${cmd}'eval $cmdThat will output Running command: cat -- $file which is probably not what you want.Or (bash specific) use printf %q to quote $file properly so its expansion can be passed to eval:printf -v cmd 'cat -- %q' $fileprintf '%s\n' Running command: $cmdeval $cmdOr, since it's a simple command here, you can store the arguments of that simple command in an array:cmd=(cat -- $file)printf 'Running command: 'printf ' %q' ${cmd[@]}printf '\n'${cmd[@]}If you know of one particular character that $file is guaranteed not to contain like : (note that : is a perfectly valid character in a file name on Unix though), you could do:cmd=cat:--:$fileIFS=:  # split on :set -f # disable glob# use the split+glob operator by leaving the variable unquoted:printf 'Running command: 'printf ' %q' $cmdprintf '\n'$cmdNow, in this specific case, you can also do:(PS4='Running command: 'set -x; cat -- $file)(beware the message goes to stderr).Or even:PS4='Running command: ' find $TARGET_DIR -type f -exec sh -xc '  cat $1' sh {} \;Or to avoid running that many sh and cat commands, use ksh93 where cat is a builtin mapped to /opt/ast/bin/cat and use the {} + syntax to run as few shells as possible:PS4='Running command: ' find $TARGET_DIR -type f -exec ksh93 -c '  PATH=/opt/ast/bin; set -x; for file do cat $file; done' ksh93 {} +To display the content of files with headers and running as few commands as possible, on GNU systems, you can also use head:$ find $TARGET_DIR -type f -exec head -vc-0 {} +==> ./file 1 <==12==> ./file 2 <==testWith zsh:zmodload zsh/mapfilefor file (**/*(D.)) printf '==> %q <==\n%s\n' $file $mapfile[$file]