Search code examples
bashescapingspecial-charactersscp

Escaping special characters in bash variables


I'm trying to read a file containing filepaths line by line and scp the files to another server, but because of certain characters in the filenames like '(', ')', '&' etc. I need to escape the input:

input.txt:

/folder1/folderA/filename+(oh+how+nice,+parantheses)

script.sh:

#!/bin/sh

promote_to=random.server.com
dev_catalog=/folderX/
uat_catalog=/folderY/

while read line
do
uat_path=$(echo "$uat_catalog$line" | sed -e "s/(/\\\(/g" | sed -e "s/)/\\\)/g")
dev_path=$(echo "$dev_catalog$line" | sed -e "s/(/\\\(/g" | sed -e "s/)/\\\)/g")

scp $dev_path user@$promote_to:$uat_path
scp $dev_path".atr" user@$promote_to:$uat_path".atr"
done < "input.txt"

Output:

-bash: /folder1/folderA/filename+(oh+how+nice,+parantheses): No such file or directory
-bash: /folder1/folderA/filename+(oh+how+nice,+parantheses): No such file or directory
usage: scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]
               [-l limit] [-o ssh_option] [-P port] [-S program]
               [[user@]host1:]file1 [...] [[user@]host2:]file2
ssh: random.server.com: Name or service not known
lost connection

Any kind of help is appreciated.


Solution

  • Part of the problem here is that the local and remote filenames are parsed differently: the local filename is used directly, so the only thing you need to do is enclose it in double-quotes (as in @Ignacio's answer), but the remote filename gets passed to a remote shell which runs it through another layer of parsing (quote and escape removal, etc). So, you want to add escapes to the remote path only. I've also taken the liberty of simplifying the sed command a little:

    #!/bin/sh
    
    promote_to=random.server.com
    dev_catalog=/folderX/
    uat_catalog=/folderY/
    
    while read line
    do
    uat_path="$(echo "$uat_catalog$line" | sed -e 's/[()&]/\\&/g')"
    dev_path="$dev_catalog$line"
    
    scp "$dev_path" "user@$promote_to:$uat_path"
    scp "$dev_path.atr" "user@$promote_to:$uat_path.atr"
    done < "input.txt"
    

    Note that the sed pattern I used, 's/[()&]/\\&/g', only escapes parentheses and ampersands; if your filenames contain any other shell metacharacters, be sure to add them to the character list in [].