1

I am trying to write a script to find a file and copy it to a particular folder. The file name has spaces and other special characters.

a=$(ls ~/Downloads/ | grep $1)
a="'$a'"
a="~/Downloads/"$a
echo $a
cp $a ~/Documents/books/.

When I quote the file name, it works fine on the terminal. But in the script, the words are treated as separate arguments. In the above script when $a is echoed out it gives out something like:

~/Downloads/'Ian Goodfellow, Yoshua Bengio, Aaron Courville - Deep Learning [pre-pub version] (2016, MIT Press).pdf'

You can see the filename is quoted. When the above argument is passed to cp on the terminal it works fine but not on the script. Please help.

1 Answers1

2

You seem to want to copy a particular file from ~/Downloads to ~/Documents/books.

The first thing to note is that ~ does not behave as a variable. In particular, it does not expand to the path of your home directory when it's quoted. In scripts, it's therefore better to use $HOME to refer to the home directory.

The second thing is that you seem to add literal single quotes to the value of your variable $a. This is not necessary, and will only result in a "No such file or directory" error (because the file does not have single quotes in the filename).

The third thing is that you should try to avoid using ls in scripts. In this case, for example, if $1 is simply the name of a file in ~/Downloads you don't have to search for it at all. Also, using grep $1 is problematic for a variety of reasons:

  • grep is primarily used on text documents. Filenames may theoretically contain newlines, which makes "grepping" filenames only work if one knows that the names or "nice".
  • $1 is unquoted an may therefore confuse grep if it contains spaces or other special characters. If $1 is *, it would expand to all filenames in the current directory, for example.
  • If the string $1 starts with a dash, it will be taken as an option to grep.

So, your script may be written as

#!/bin/sh

name="$HOME/Downloads/$1"

printf 'The filename is "%s"\n' "$name"

cp "$name" "$HOME/Documents/books"

The printf command here is to output the name of the file that we picked. I'm outputting double quotes around the name, but the name does not have double quotes in it. Instead, I'm making sure to quote $name in the call to cp on the last line. In fact, I'm making sure to quote all expansions that I know would otherwise result in the shell splitting up the variable's value.

You would use that script as

./script.sh 'Ian Goodfellow, Yoshua Bengio, Aaron Courville - Deep Learning [pre-pub version] (2016, MIT Press).pdf'

The name of the file has to be quoted since it contains spaces and characters that are otherwise special in the shell.

If you want your script to look for a file that contains a word given on the command line, so that you could say

./script.sh Goodfellow

Then use the given string in a globbing pattern:

#!/bin/sh

for name in "$HOME/Downloads"/*"$1"*.pdf; do
    printf 'Will copy the file "%s"\n' "$name"
    cp "$name" "$HOME/Documents/books"
done

Now the script will take the first argument and loop over all .pdf files in ~/Downloads that contain the given string in the filename. Each such file will be copied to ~/Documents/books.

If you're on a Unix where cp -v will verbosely copy files, the script may be shrunk down further:

#!/bin/sh

cp -v "$HOME/Downloads"/*"$1"*.pdf "$HOME/Documents/books"

This prevents you from formatting the output yourself ("Will copy ...") as we're no longer looping over the filenames matching the given pattern. Instead we just rely on the fact that if you give multiple filenames to cp (and the globbing pattern may well expand to multiple filenames), then these can be copied with a single command to some destination directory.


See also:

Kusalananda
  • 333,661
  • Additionally, with cp from GNU coreutils: find "$HOME/Downloads" -name "*$1*" -exec cp -vt "$HOME/Documents/books" '{}' + – glenn jackman Apr 15 '20 at 16:19
  • Notice that $name is enclosed in double quotes ". I suspect the OPs original script will also work by changing $a~ to"$a"`. @Kuslananda answer is more elegant, IMHO. – Scottie H Apr 15 '20 at 18:34
  • @ScottieH Unfortunately not. If the grep finds more than a single file, if the argument in $1 contains spaces, or if $1 contains globbing characters, then their script may break down. Also, the fact that they artificially add single quotes to the value in $a guarantees that it won't work. I believe they are confusing the output of echo with what will actually be used by cp. – Kusalananda Apr 15 '20 at 19:08