Search code examples
shellvariablesargumentswhitespacefilenames

Command in variable doesn't run with witespaces in filenames


I like the possibility in linux shell to store a command into a variable:

mycommand="cp fileOne.txt fileTwo.txt /home/myself/targetdir"
$mycommand

That's executes well.

But how to handle whitspaces inside the filenames?

The following alternatives runns well:

cp file\ One.txt file\ Two.txt /home/myself/targetdir
# and also
cp "file One.txt" "file Two.txt" /home/myself/targetdir

Now I try to put this into my variable too (To call $mycommand).

But none of my following trys run:

mycommand="cp file\ One.txt file\ Two.txt /home/myself/targetdir"
mycommand="cp \"file One.txt\" \"file Two.txt\" /home/myself/targetdir"
mycommand="cp 'file One.txt' 'file Two.txt' /home/myself/targetdir"
mycommand='cp "file One.txt" "file Two.txt" /home/myself/targetdir'

In all options the arguments are sepparated by whitespaces, the file "file" will not be found.

What can I do instead?

(I'm wondering why I cannot find similar question already taken about this...)

EDIT:

With set -x I get the following lines for cp, depending from the try above:

+ cp 'file\' One.txt 'file\' Two.txt /home/myself/targetdir
+ cp '"file' 'One.txt"' '"file' 'Two.txt"' /home/myself/targetdir
+ cp ''\''file' 'One.txt'\''' ''\''file' 'Two.txt'\''' /home/myself/targetdir
+ cp '"file' 'One.txt"' '"file' 'Two.txt"' /home/myself/targetdir

The first Output line for each try, translated from german to english, are:

cp: cannot stat 'file\': No such file or directory
cp: cannot stat '"file'’: No such file or directory
cp: cannot stat ''\''file': No such file or directory
cp: cannot stat '"file': No such file or directory

It seems that the command string is separated by whitespaces, ignoring "" and \ - but why? And how can I avoid this?


Solution

  • I found the solution:

    eval $mycommand
    

    and, probably more secure for other cenarios,

    eval "$mycommand"
    

    will work as expected.

    (With eval variables will be expanded before executing.)

    An example of practical use:

    Given a tool to concatenate pdf files like pdfunite:

    for file in *.pdf; do
        inputfiles="$inputfiles \"$file\""
    done
    eval "pdfunite $inputfiles mergedOutputfile.pdf"
    

    Edit:

    Seeing this old post, I'm adding the better solution I'm using now - already mentioned in the link posted by @chepner:

    use an array:

    inputfiles=()
    for file in *.pdf; do
        inputfiles+=("$file")
    done
    pdfunite "${inputfiles[@]}" "/path/to/merged outputfile.pdf"
    

    (It doesn't seem to answer the first question, but in many cases this scenario is the origin of the question)