M.A.G
M.A.G

Reputation: 361

Escape filenames the same way Bash does it

When I use the "tab" key in bash (when you have started to type the filename and you want it to complete), bash escapes the filename correctly, and if I use exactly that "escaped" filename, it works.

For Instance:

An-Beat - Mentally Insine (Original Mix).mp3 => After bash Escapes It Using "TAB" An-Beat\ -\ Mentally\ Insine\ \(Original\ Mix\).mp3

I'm search for a function for bash that will escape a filename the same way "tab" escapes filenames.

Upvotes: 35

Views: 43136

Answers (7)

Aeronautix
Aeronautix

Reputation: 316

I'm search for a function for bash that will escape a filename the same way "tab" escapes filenames.

Solution to get escaped full paths

I've created a portable function to get all the escaped paths to all items in the current directory, tested on macOS and Linux (requires GNU bash installed).

escpaths() {
    find "$PWD" -maxdepth 1 -print0 | xargs -0 -I {} bash -c 'printf "%q\n" "$0"' {} | sort
}

Here's a rigorous test case scenario:

# Generate test directory, containing test directories and files.
mkdir 'test dir'; cd 'test dir'

mkdir '\dir  with\ backslashes\\\'
touch '\file with \\backslashes\'
touch 'An-Beat - Mentally Insine (Original Mix).mp3'
mkdir 'crazy(*@$):{}[]dir:'
mkdir 'dir with 字 chinese 鳥鸟 characters'
touch $'file\twith\tmany\ttabs.txt'
touch 'file @*&$()!#[]:.txt'
touch 'file
with
newlines.txt'

Executing escpaths in the test directory test dir gives the escaped output:

$'/.../test dir/file\nwith\nnewlines.txt'
$'/.../test dir/file\twith\tmany\ttabs.txt'
/.../test\ dir
/.../test\ dir/An-Beat\ -\ Mentally\ Insine\ \(Original\ Mix\).mp3
/.../test\ dir/\\dir\ \ with\\\ backslashes\\\\\\
/.../test\ dir/\\file\ with\ \\\\backslashes\\
/.../test\ dir/crazy\(\*@\$\):\{\}\[\]dir:
/.../test\ dir/dir\ with\ 字\ chinese\ 鳥鸟\ characters
/.../test\ dir/file\ @\*\&\$\(\)\!#\[\]:.txt

Solution to get escaped basenames only

This (also portable) function will get you the escaped basenames of all items in the current directory (this time excluding the current directory).

escbasenames() {
    find . -mindepth 1 -maxdepth 1 -exec printf '%s\0' "$(basename {})" \; | xargs -0 -I {} bash -c 'printf "%q\n" "${0#./}"' {} | sort
}

Running escbasenames in the same test directory test dir gives the escaped basenames:

$'file\nwith\nnewlines.txt'
$'file\twith\tmany\ttabs.txt'
An-Beat\ -\ Mentally\ Insine\ \(Original\ Mix\).mp3
\\dir\ \ with\\\ backslashes\\\\\\
\\file\ with\ \\\\backslashes\\
crazy\(\*@\$\):\{\}\[\]dir:
dir\ with\ 字\ chinese\ 鳥鸟\ characters
file\ @\*\&\$\(\)\!#\[\]:.txt

Final notes

Note that if the path/filename contains newlines or tabs, it will be encoded as an ANSI-C string. Autocompletion in the terminal also completes with ANSI-C strings. Example ANSI-C string autocompletion outputs would look like my$'\n'newline$'\n'dir/ or my$'\t'tabbed$'\t'file.txt.

Upvotes: 1

dubsauce
dubsauce

Reputation: 43

I may be a little late to the party but what worked for me is:

ls  --quoting-style=shell-escape

This way it also escapes characters like ! or '.

Upvotes: 2

Shen Wei
Shen Wei

Reputation: 41

ls  --quoting-style=escape /somedir

this will output the escaped filenames, and also work with unicode characters, printf method does not work with Chinese, it outputs something like $'\206\305...'

Upvotes: 4

panticz
panticz

Reputation: 2315

The solution from "sehe" works fine, in addition, you can also use double quotes (") instead of single apostrophe (') to by able to use variables:

x="a real \good %* load of crap from ${USER}"
echo $(printf '%q' "$x")

Of course the string may not contain $ or " itself or you have to escape those manulally by splash \$.

Upvotes: 4

bvarner
bvarner

Reputation: 370

I'm going to elaborate on sehe's response on this one.

If you want to pass the argument to be converted as a shell script parameter, encase the parameter in "'s.

#!/bin/bash
x=$(printf '%q' "$1")
echo $x

I really like the printf solution, since it does every special character, just like bash.

Upvotes: 10

sehe
sehe

Reputation: 392929

Use printf (1):

x='a real \good %* load of c$rap'
x=$(printf '%q' "$x")
echo $x

will return

a\ real\ \\good\ %\*\ load\ of\ c\$rap

Upvotes: 50

kurumi
kurumi

Reputation: 25599

$ string="An-Beat - Mentally Insine (Original Mix).mp3"
$ echo ${string// /\\ }
An-Beat\ -\ Mentally\ Insine\ (Original\ Mix).mp3
$ string=${string// /\\ }
$ echo ${string//(/\\( }
An-Beat - Mentally Insine \( Original Mix).mp3

Upvotes: 4

Related Questions