A Way to Prevent Bash from Parsing Command Line W/Out Using Escape Symbols

Which characters need to be escaped when using Bash?

There are two easy and safe rules which work not only in sh but also bash.

1. Put the whole string in single quotes

This works for all chars except single quote itself. To escape the single quote, close the quoting before it, insert the single quote, and re-open the quoting.

'I'\''m a s@fe $tring which ends in newline
'

sed command: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Escape every char with a backslash

This works for all characters except newline. For newline characters use single or double quotes. Empty strings must still be handled - replace with ""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

sed command: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. More readable version of 2

There's an easy safe set of characters, like [a-zA-Z0-9,._+:@%/-], which can be left unescaped to keep it more readable

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

sed command: LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Note that in a sed program, one can't know whether the last line of input ends with a newline byte (except when it's empty). That's why both above sed commands assume it does not. You can add a quoted newline manually.

Note that shell variables are only defined for text in the POSIX sense. Processing binary data is not defined. For the implementations that matter, binary works with the exception of NUL bytes (because variables are implemented with C strings, and meant to be used as C strings, namely program arguments), but you should switch to a "binary" locale such as latin1.


(You can easily validate the rules by reading the POSIX spec for sh. For bash, check the reference manual linked by @AustinPhillips)

Bash) Is it possible to pass argument with special character without escaping or single quoting?

(I don't have a Windows setup to test with, but assuming it just fills in the raw path when you drag-and-drop files, what I suggest here should work.)

Everything entered on a shell command line -- including arguments to your script -- need to conform to shell syntax, which assigns special meaning to backslashes, spaces, and a bunch of other characters you might find in a Windows file path. In order to be passed through to the script as literal characters, they must be quoted or escaped as per the rules of shell syntax.

But there is another way... Rather than passing the file path as an argument, you could have the script read it as input after you start the script. Since the script would be reading it directly, the script can control what syntax is or isn't needed. In particular, it can use read -r, where the -r means "raw mode", which doesn't mess with backslashes. Something like this:

#!/bin/bash

id=user
pc_ip=xxx.xxx.xxx.xxx
pc_port=xxx

read -r -p "File: " filepath

scp -P "$pc_port" -r "$id@$pc_ip:$filepath" .

Note that I also switched to lowercase variable names (lower- and mixed-case variable names are safer, because a bunch of all-caps names have special meanings that can cause trouble). I also double-quoted all the variable references, to avoid weird parsing of spaces and maybe other things (you should almost always do this).

You could also make the script accept either a command-line argument (which would have to be quoted/escaped) or if that's not supplied, take it as input instead:

...
if [[ -n "$1" ]]; then
filepath=$1
else
read -r -p "File: " filepath
fi
...

How to interpret special characters in command line argument in C?

Regarding the second problem: as @Jims said, you could use printf(1) to print the string. Good idea, but be aware that that would work only because the escapes you (appear to) want to recognise, like \t and \n, are the same as the ones that printf recognises.

These escapes are common, but there's nothing fundamental about them, so the general answer to your question – and the answer if you want to recognise an escape that printf doesn't – is for your program to interpret them. The C-like code to do that would be something like:

char *arg = "foo\\tbar\\n"; /* the doubled backslashes are to make this valid C */
int arglen = strlen(arg);
for (i=0; i<arglen; i++) {
if (arg[i] == '\\') { // we've spotted an escape
// interpret a backslash escape
i++; // we should check for EOS here...
switch (arg[i]) {
case 'n': putchar('\n'); break;
case 't': putchar('\t'); break;
// etc
}
} else {
// ordinary character: just output it
putchar(arg[i]);
}
}

(insert error handling and good style, to taste).

How to escape a variable in Bash when passing to a command-line argument

There's no good way of doing this without an array. (There's a not good way of doing it, using eval, but the array is simpler.)

The problem is that you want each file to end up as one argument to email, but you want to accumulate all those arguments into a Bash variable. There's just no way to do that: you can cause the Bash variable to be inserted as a single argument ("$arg") or you can cause it to be inserted as however many words it gets split into ($arg), but you can't get it to be split according to whether or not spaces were escaped when the variable was created, because Bash only remembers the string assigned to a variable, not the escape marks.

However, you can do it with an array, because you can make every filename exactly one array element, and you can get Bash to insert an array as one argument per element.

Here's how:

# File paths                                                                                                                              
destinationPath="/cygdrive/c/Documents and Settings/Eric/My Documents/"
attachments="\n2013-12-12.pdf"
body="Test Body"
recipient="asdf@asdf.com"

# Prepare attachments
args=()
for file in $attachments ; do
file=${file//[ \\n]/}
touch $file
mv $file "$destinationPath/$file"
args+=(-a "$destinationPath/$file")
done

# Send email
echo -e $body |
email --from-addr nosender@mydomain.com \
--from-name "Automated CSS Downloader" \
--subject "Downloaded new file(s) from CSS" \
"${args[@]}" eric@mydomain.com

You might also want to make the attachments variable into an array; from the presence of the newline character, I'm assuming that you're actually setting it in some more complicated way, so I didn't change the code. But your current code won't work if the attachment name has a space in it (and I'm pretty sure that the newline will be eliminated by the parameter expansion in the for form, unless you've altered the value of $IFS, so file=${file//[ \\n]/} shouldn't be necessary.)

bash tips needed for understanding how to escape characters in command-line

Single quotes inhibit all escaping and substitution:

echo '$hello world!'

You can alternate or disable quoting if you need to mix things up:

echo '$5.00 on '"$horse"'!'
ls -ld ~/'$$'/*

Interpreting escapes is also easy:

echo $'Wake up!\7'
sort -n <<< $'4\n3\n8'

Command to escape a string in bash

In Bash:

printf "%q" "hello\world" | someprog

for example:

printf "%q" "hello\world"
hello\\world

This could be used through variables too:

printf -v var "%q\n" "hello\world"
echo "$var"
hello\\world


Related Topics



Leave a reply



Submit