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.
When a text string contains characters that have a special functionality (%, *, +, .), the characters should be "escaped" with a backslash ( \ ). For instance, %, *, +, .). In this way, the characters will be interpreted literally, and not with their special meaning.
Escape characters. Escape characters are used to remove the special meaning from a single character. A non-quoted backslash, \, is used as an escape character in Bash. It preserves the literal value of the next character that follows, with the exception of newline.
$1 means an input argument and -z means non-defined or empty. You're testing whether an input argument to the script was defined when running the script.
bash [filename] runs the commands saved in a file. $@ refers to all of a shell script's command-line arguments. $1 , $2 , etc., refer to the first command-line argument, the second command-line argument, etc. Place variables in quotes if the values might have spaces in them.
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 []
.
The other way around would be to NOT escape special chars but to quote the value in a format that can be re-used as an input instead (modern Bash is implied, e.g. version 5 and higher):
> VAR='/folder1/folderA/filename+(oh+how+nice,+parantheses)'
> echo $VAR
/folder1/folderA/filename+(oh+how+nice,+parantheses)
> echo ${VAR@Q}
'/folder1/folderA/filename+(oh+how+nice,+parantheses)'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With