22

Assuming $file holding a value of a file name, say Dr' A.tif. In bash programming, how could I escape single quote and any other special character of the $file without removing the special character?

Update on 9 July 2014

As request from @Gilles, following code snippet that doesn't able to handle Dr' A.tif:

files=$(find /path/ -maxdepth 1 -name "*.[Pp][Dd][Ff]" -o -name "*.[Tt][Ii][Ff]")
echo "${files}" > ${TEMP_FILE}
while read file
do
   newfile=$(echo "${file}" | sed 's, ,\\ ,g') ## line 1
done < ${TEMP_FILE}

After I have tried out the answer from @Patrick on line 1, it seems to work for me. But if I have file such as Dr\^s A.tif, printf command doesn't seem help, it shows me Dr\^s\ A.tif. If I manually try it on console like this:

printf "%q" "Dr\^s A.tif"

I will have this output:

Dr\\\^s\ A.tif

Any idea how to handle this?

huahsin68
  • 1,917

5 Answers5

25

You can use the printf builtin with %q to accomplish this. For example:

$ file="Dr' A.tif"
$ printf '%q\n' "$file"
Dr\'\ A.tif

$ file=' foo$bar\baz`'
$ printf '%q\n' "$file"
\ foo\$bar\\baz\`

From the bash documentation on printf:

In addition to the standard format specifications described in printf(1)
and printf(3), printf interprets:

%b expand backslash escape sequences in the corresponding argument %q quote the argument in a way that can be reused as shell input %(fmt)T output the date-time string resulting from using FMT as a format string for strftime(3)

wjandrea
  • 658
phemmer
  • 71,831
  • 1
    This doesn't work with certain cases .e.g. when you need to preserve double-quotes. – user3467349 Nov 07 '15 at 06:08
  • 1
    @user3467349 it works just fine with quotes. I'm betting you're trying something like printf '%s' "foo". You need to understand how argument parsing works in the shell first. See https://www.gnu.org/software/bash/manual/bash.html#Shell-Operation #2 happens before #6. – phemmer Nov 07 '15 at 18:49
  • Could you provide an example of this string printed with printf and no other manipulations It doesn't have a: "" – user3467349 Nov 08 '15 at 04:25
  • Your issue has nothing to do with printf. Your issue is that you don't want the shell to parse your string. In order to do that, you have to pass your input to the shell in a way where it doesn't even try to parse it. One way to do this would be read -r -p 'input: ' && printf '%q\n' "$REPLY", and provide the input when prompted. – phemmer Nov 08 '15 at 06:48
  • Provide input while prompted won't work well in a shell script, neither is it speedy even interactively. Hence my above comment about this being a limited / sub-optimal solution. – user3467349 Nov 08 '15 at 10:10
  • 1
    As i've said several times now. Not knowing how how to pass raw data to the shell isn't an issue with printf. Perhaps you should ask a question rather than critiquing a solution that has nothing to do with your problem. – phemmer Nov 08 '15 at 15:16
  • You can use cat <<EOF; (as I wrote below) which seems preferable to me since it will handle edge-cases which your answer doesn't support. I should point out that How to escape special characters in a string? is a generic search-phrase that will cause people to stumble on this question and it makes sense to provide an answer that will cover cases which fit the title (even if it's not exactly what OP was asking). – user3467349 Nov 08 '15 at 15:38
  • For a more modern version of this answer without printf, see https://stackoverflow.com/a/27817504/442022 . – colan Aug 23 '21 at 17:53
4

Try:-

file=Dr\'\ A.tif
echo $file
Dr' A.tif

or

file="Dr' A.tif"
echo $file
Dr' A.tif

or if the string contains a double quote:-

file='Dr" A.tif'
echo $file
Dr" A.tif

There are good tutorials on escaping and quoting on the net. Start with this one.

garethTheRed
  • 33,957
1

You don't need to escape any file names you are handling in a script. Escaping is only necessary if you want to put a file name as a literal in a script, or to pass several file names as a single input stream to another script.

Since you're looping through the output of find, this is one of the simplest ways (!) to handle every possible path:

while IFS= read -r -d ''
do
    file_namex="$(basename -- "$REPLY"; echo x)"
    file_name="${file_namex%$'\nx'}"
    do_something -- "$file_name"
done < <(find "$some_path" -exec printf '%s\0' {} +)
l0b0
  • 51,350
-1

quick and (very) dirty

find . | sed 's/^/"/' | sed 's/$/"/'
chaos
  • 48,171
-2

A lot of these answers, including the top-voted one using printf "%q", will not work in all cases without additional manipulation.  I would suggest the following (example below):

cat <<EOF
2015-11-07T03:34:41Z app[postgres.0000]: [TAG] text-search query doesn't contain lexemes: ""
EOF