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 reopen the quoting.

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

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

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/^/"/; /"/'.

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/^/"/; /"/'.


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.)

Answer from Jo So on Stack Overflow
🌐
Opensource.com
opensource.com › article › 23 › 2 › escape-sequences-linux-shell
5 escape sequences for your Linux shell | Opensource.com
February 16, 2023 - A \f form feed signal is like a newline character, but without the imperative to return to column 0. Here's a printf command using a form feed instead of a newline: ... Your shell prompt is on the next line, but not at the start of the line. There are two tab escape sequences: the \t horizontal tab and the \v vertical tab.
🌐
Linux Manual Page
linux.die.net › abs-guide › escapingsection.html
Escaping
Escaping is a method of quoting single characters. The escape (\) preceding a character tells the shell to interpret that character literally.
Discussions

linux shell escape - execute commands without letters or backslashes
With this timing.. it looks like your question is how to solve the exact no letters challenge from picoCTF More on reddit.com
🌐 r/hacking
11
12
March 13, 2024
bash - Escaping a linux command - Unix & Linux Stack Exchange
I'm trying to run a command from a script I wrote that basically replaces a string in file vzctl exec VZID sed -i 's/\/>/address="$IP\/255.255.255.0"\/\>/' "file.xml" The problem is the comm... More on unix.stackexchange.com
🌐 unix.stackexchange.com
August 14, 2015
bash - What characters are required to be escaped in command line arguments? - Unix & Linux Stack Exchange
In Bash, when specifying command line arguments to a command, what characters are required to be escaped? Are they limited to the metacharacters of Bash: space, tab, |, &, ;, (, ), More on unix.stackexchange.com
🌐 unix.stackexchange.com
March 20, 2016
shell - How do I exit or cancel a bad bash command? - Unix & Linux Stack Exchange
Ctrl + C is the usually-reliable ... the INT signal, as described in other comments and the accepted answer. ... You can always try the obvious things like ^C, ^D (eof), Escape etc., but if all fails I usually end up suspending the command with ^Z (Control-Z) which puts me ... More on unix.stackexchange.com
🌐 unix.stackexchange.com
August 16, 2012
Top answer
1 of 7
403

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 reopen the quoting.

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

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

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/^/"/; /"/'.

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/^/"/; /"/'.


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.)

2 of 7
117

Format that can be reused as shell input

Edit February 2021: Bash ${var@Q}

Under Bash, you could store your variable content with Parameter Expansion's @ command for Parameter transformation:

${parameter@operator}
       Parameter transformation.  The expansion is either a > transforma‐
       tion of the value of parameter or  information  about  parameter
       itself,  depending on the value of operator.  Each operator is a
       single letter:

       Q      The expansion is a string that is the value of  parameter
              quoted in a format that can be reused as input.
...
       A      The  expansion  is  a string in the form of an assignment
              statement or declare command  that,  if  evaluated,  will
              recreate parameter with its attributes and value.

Sample:

'Hello\nGood world.\n'
$ echo "$var"
Hello
Good world.

$ echo "'Hello\nGood world.\n'

$ echo "${var@A}"
var=$'Hello\nGood world.\n'

Old answer

There is a special printf format directive (%q) built for this kind of request:

printf [-v var] format [arguments]

    %q     causes printf to output the corresponding argument
           in a format that can be reused as shell input.

Some samples:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

This could be used through variables too:

printf -v var "%q" "$foo
"
echo "'Hello world\n'

Quick check with all (128) ASCII bytes:

Note that all bytes from 128 to 255 have to be escaped.

This little loop will print all characters from 0x00 to 0x7f, by using both: printf %q and ${var@Q} method.

for i in {0..127}; do
    printf -v var %02X $i
    printf -v var %b \\x$var
    sign=E
    printf -v res %q "$var"
    [[ $var == "$res" ]] && sign=-
    printf '%02X %s %-*s %-*s\n' $i $sign $(( 31 < i && i < 96 ? 2 : 8
        )) "${res}" $(( 31 < i && i < 96 ? 3 : 8)) "${var@Q}"
done |
    pr -w100 --sep-string='' -t4 |
    sed 's/\o11\+/\o11/g'

This should render something like:

00 E ''       ''         20 E \  ' '      40 - @  '@'      60 E \`       '`'
01 E '\001'    21 E \! '!'      41 - A  'A'      61 - a        'a'
02 E '\002'    22 E \" '"'      42 - B  'B'      62 - b        'b'
03 E '\003'    23 E \# '#'      43 - C  'C'      63 - c        'c'
04 E '\004'    24 E \'      44 - D  'D'      64 - d        'd'
05 E '\005'    25 - %  '%'      45 - E  'E'      65 - e        'e'
06 E '\006'    26 E \& '&'      46 - F  'F'      66 - f        'f'
07 E '\a'      27 E \' \'       47 - G  'G'      67 - g        'g'
08 E '\b'      28 E \( '('      48 - H  'H'      68 - h        'h'
09 E '\t'      29 E \) ')'      49 - I  'I'      69 - i        'i'
0A E '\n'      2A E \* '*'      4A - J  'J'      6A - j        'j'
0B E $'\v'    $'\v'      2B - +  '+'      4B - K  'K'      6B - k        'k'
0C E $'\f'    $'\f'      2C E \, ','      4C - L  'L'      6C - l        'l'
0D E '\r'      2D - -  '-'      4D - M  'M'      6D - m        'm'
0E E '\016'    2E - .  '.'      4E - N  'N'      6E - n        'n'
0F E '\017'    2F - /  '/'      4F - O  'O'      6F - o        'o'
10 E '\020'    30 - 0  '0'      50 - P  'P'      70 - p        'p'
11 E '\021'    31 - 1  '1'      51 - Q  'Q'      71 - q        'q'
12 E '\022'    32 - 2  '2'      52 - R  'R'      72 - r        'r'
13 E '\023'    33 - 3  '3'      53 - S  'S'      73 - s        's'
14 E '\024'    34 - 4  '4'      54 - T  'T'      74 - t        't'
15 E '\025'    35 - 5  '5'      55 - U  'U'      75 - u        'u'
16 E '\026'    36 - 6  '6'      56 - V  'V'      76 - v        'v'
17 E '\027'    37 - 7  '7'      57 - W  'W'      77 - w        'w'
18 E '\030'    38 - 8  '8'      58 - X  'X'      78 - x        'x'
19 E '\031'    39 - 9  '9'      59 - Y  'Y'      79 - y        'y'
1A E '\032'    3A - :  ':'      5A - Z  'Z'      7A - z        'z'
1B E '\E'      3B E \; ';'      5B E \[ '['      7B E \{       '{'
1C E '\034'    3C E \< '<'      5C E \\ '\'      7C E \|       '|'
1D E '\035'    3D - =  '='      5D E \] ']'      7D E \}       '}'
1E E '\036'    3E E \> '>'      5E E \^ '^'      7E E \~       '~'
1F E '\037'    3F E \? '?'      5F - _  '_'      7F E $'\177'  $'\177'

Where

  • first field is hexadecimal value of byte,
  • second contain E if character need to be escaped,
  • third field show escaped presentation of character and
  • last field show useable version printed by ${var@Q} syntax.

Small function looking for limited bunch of characters

For fun, here is another way for looping over a string, grouping all characters by the need to be escaped.

specialCharsFromString() {
    local {q,}char bunch{_0,_1} \
        special="${1:-'\`\"/\!@#\$%^&*()-_+={\}[]|;:,.<>? '}"
    while IFS= LANG=C LC_ALL=C read -d '' -rn 1 char; do
        printf -v qchar %q "$char"
        [[ $char == "$qchar" ]]
        local -n bunch=bunch_$?
        bunch+=(${char@Q})
    done < <(printf %s "$special");
    printf 'Characters who %sneed to be escaped:\n%s\n' \
        "doesn't " "${bunch_0[*]}" "" "${bunch_1[*]}"
}
specialCharsFromString $'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
Characters who doesn't need to be escaped:
'@' '%' '-' '_' '+' '=' ':' '.' '/'
Characters who need to be escaped:
'`' '!' '#' '$' '^' '&' '*' '(' ')' '{' '}' '|' '[' ']' '\' ';' \' '"' ',' '<' '>' '?' ' '

Why ,?

You could see some characters that don't always need to be escaped, like ,, } and {.

So not always, but sometimes:

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

or

echo test { 1, 2, 3 }
test { 1, 2, 3 }

but care:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3

See Brace Expansion chapter in Bash's man page:

  man -P'less +/Brace\ Expansion' bash

Note about percent sign %.

No, percent sign don't need to be escaped in any POSIX shell compatible!

But in cron's crontab!! This is a common issue as syntax used in crontab is mostly POSIX shell compatible. but man -Pless\ +/% 5 crontab:

... Percent-signs (%) in the command, unless escaped with backslash (), will be changed into newline characters...

If you try to use timestamp is crontab, you have to escape %:

* * * * * echo one more line >>file-$(date +\%F).log

Note about dashes sign -.

There isn't any reason to escape a dash in a string except if you try to use them at begin of string as the first argument of a regular command:

printf '- %3d %s\n' $((count++)) "Some String"
bash: printf: - : invalid option

ls -dashedFilename
ls: invalid option -- 'e'

Even escaped, this won’t go better:

ls \-dashedFilename
ls: invalid option -- 'e'

For this, the recommended way is to use double dash --:

From POSIX.1-2017 Utility Conventions Guideline 10:

Guideline 10:

The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the - character.

printf  -- '- %3d %s\n' $((count++)) "Some String"
-   2 Some String

ls -- -dashedFilename
ls: cannot access '-dashedFilename': No such file or directory

Ok, they don't exist.

Alternative: Simply avoid dashes at begin of any string:

printf '\55 %3d %s\n' $((count++)) "Some other String"
-   3 Some other String
printf '\x2d %3d %s\n' $((count++)) "Some other String"
-   4 Some other String

Using any of octal or hexadecimal representation of dash

ls ./-dashedFilename
ls: cannot access './-dashedFilename': No such file or directory

Using relative or full path.

🌐
Cloudaffle
cloudaffle.com › escaping special characters
Escape Characters and Escape Sequences in Linux | Cloudaffle | Everything About Web Development, JavaScript Tutorials, Tips and Tricks
You can also use octal and hexadecimal representations for ASCII characters: echo -e "\x48\x65\x6c\x6c\x6f" # Prints "Hello" using hexadecimal ASCII codes · Let's say you need to display a string with quotes around a word.
🌐
Baeldung
baeldung.com › home › scripting › escaping characters in bash
Escaping Characters in Bash | Baeldung on Linux
February 25, 2026 - In this example, the so-called control sequence introducer <ESC><left-square-bracket> starts the K command with an argument of 1. Thus, it clears all characters from the beginning of the current line. Because of this, the word TESTING does not show up in the prompt. As already mentioned, ANSI and ANSI-C escape sequences are used throughout the Linux ecosystem.
Find elsewhere
🌐
Javatpoint
javatpoint.com › linux-escaping-special-characters
Linux Escaping Special Characters - javatpoint
Linux Escaping Special Characters for beginners and professionals with examples on files, directories, permission, backup, ls, man, pwd, cd, chmod, man, shell, pipes, filters, regex, vi etc..
🌐
Shell Scripting Tutorial
shellscript.sh › escape.html
Escaping special characters in the shell - The Shell Scripting Tutorial
Written by Steve Parker MSc, Senior DevOps Engineer with 30+ years of Unix, Linux and automation experience, and author of Shell Scripting: Expert Recipes for Linux, Bash and more. Certain characters are significant to the shell; we have seen, for example, that the use of double quotes (") characters affect how spaces and TAB characters are treated, for example: $ echo Hello World Hello World $ echo "Hello World" Hello World ... The first and last " characters wrap the whole lot into one parameter passed to echo so that the spacing between the two words is kept as is.
🌐
Onlinelinuxtools
onlinelinuxtools.com › escape-shell-characters
Escape Shell Characters - Online Linux Tools
Everything that is escaped by this command is also escaped by this tool. A backslash preserves the literal value of the next character (with the exception of \n). By default, the %q format also escapes spaces and commas but often that is not really needed, so we have added an option to skip escaping spaces and commas. In case you have multiple strings to escape, we have also made it easy to skip escaping newlines.
🌐
Ubuntu
manpages.ubuntu.com › trusty › man(1)
Ubuntu Manpage: escape - escape shell special characters in a string
escape prepends a "\" character to all shell special characters in string, making it safe to compose a shell command with the result.
🌐
SS64
ss64.com › bash › syntax-quoting.html
How-To: Use Escape Characters, delimiters and Quotes - Linux
Each of the shell metacharacters has special meaning to the shell and must be quoted if it is to represent itself. A non-quoted backslash \ is the Bash escape character. It preserves the literal value of the next character that follows, with the exception of newline.
Top answer
1 of 3
35

The following characters have special meaning to the shell itself in some contexts and may need to be escaped in arguments:

Character Unicode Name Usage
` U+0060 (Grave Accent) Backtick Command substitution
~ U+007E Tilde Tilde expansion
! U+0021 Exclamation mark History expansion
# U+0023 Number sign Hash Comments
$ U+0024 Dollar sign Parameter expansion
& U+0026 Ampersand Background commands
* U+002A Asterisk Filename expansion and globbing
( U+0028 Left Parenthesis Subshells
) U+0029 Right Parenthesis Subshells
U+0009 Tab () Word splitting (whitespace)
{ U+007B Left Curly Bracket Left brace Brace expansion
[ U+005B Left Square Bracket Filename expansion and globbing
| U+007C Vertical Line Vertical bar Pipelines
\ U+005C Reverse Solidus Backslash Escape character
; U+003B Semicolon Separating commands
' U+0027 Apostrophe Single quote String quoting
" U+0022 Quotation Mark Double quote String quoting with interpolation
U+000A Line Feed Newline Line break
< U+003C Less than Input redirection
> U+003E Greater than Output redirection
? U+003F Question mark Filename expansion and globbing
U+0020 Space Word splitting1 (whitespace)

Some of those characters are used for more things and in more places than the one I linked.


There are a few corner cases that are explicitly optional:

  • ! can be disabled with set +H, which is the default in non-interactive shells.
  • { can be disabled with set +B.
  • * and ? can be disabled with set -f or set -o noglob.
  • = Equals sign (U+003D) also needs to be escaped if set -k or set -o keyword is enabled.

Escaping a newline requires quoting — backslashes won't do the job. Any other characters listed in IFS will need similar handling. You don't need to escape ] or }, but you do need to escape ) because it's an operator.

Some of these characters have tighter limits on when they truly need escaping than others. For example, a#b is ok, but a #b is a comment, while > would need escaping in both contexts. It doesn't hurt to escape them all conservatively anyway, and it's easier than remembering the fine distinctions.

If your command name itself is a shell keyword (if, for, do) then you'll need to escape or quote it too. The only interesting one of those is in, because it's not obvious that it's always a keyword. You don't need to do that for keywords used in arguments, only when you've (foolishly!) named a command after one of them. Shell operators ((, &, etc) always need quoting wherever they are.


1Stéphane has noted that any other single-byte blank character from your locale also needs escaping. In most common, sensible locales, at least those based on C or UTF-8, it's only the whitespace characters above. In some ISO-8859-1 locales, U+00A0 no-break space is considered blank, including Solaris, the BSDs, and OS X (I think incorrectly). If you're dealing with an arbitrary unknown locale, it could include just about anything, including letters, so good luck.

Conceivably, a single byte considered blank could appear within a multi-byte character that wasn't blank, and you'd have no way to escape that other than putting the whole thing in quotes. This isn't a theoretical concern: in an ISO-8859-1 locale from above, that A0 byte which is considered a blank can appear within multibyte characters like UTF-8 encoded "à" (C3 A0). To handle those characters safely you would need to quote them "à". This behaviour depends on the locale configuration in the environment running the script, not the one where you wrote it.

I think this behaviour is broken multiple ways, but we have to play the hand we're dealt. If you're working with any non-self-synchronising multibyte character set, the safest thing would be to quote everything. If you're in UTF-8 or C, you're safe (for the moment).

2 of 3
3

In GNU Parallel this is tested and used extensively:

$a =~ s/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\'\202-\377]/\\$&/go;
# quote newline as '\n'                                                                                                         
$a =~ s/[\n]/'\n'/go;

It is tested in bash,dash,ash,ksh,zsh, and fish. Some of the characters do not need quoting in some (versions) of the shells, but the above works in all tested shells.

If you simply want a string quoted, you can pipe it into parallel --shellquote:

printf "&*\t*!" | parallel --shellquote
🌐
GNU
gnu.org › software › bash › manual › html_node › Escape-Character.html
Escape Character (Bash Reference Manual)
Next: Single Quotes, Up: Quoting [Contents][Index] A non-quoted backslash ‘\’ is the Bash escape character. It preserves the literal value of the next character that follows, removing any special meaning it has, with the exception of newline.
🌐
Linuxize
linuxize.com › home › bash › echo command in linux: print text and variables
echo Command in Linux: Print Text and Variables | Linuxize
May 9, 2026 - The command prints the text exactly as it was passed to echo and adds a newline at the end. Although not required, it is a good practice to enclose arguments in double or single quotes. When using single quotes '', the literal value of each character is preserved and variables are not expanded. To print a double quote, enclose the text within single quotes or escape it with a backslash:
🌐
LinuxVox
linuxvox.com › blog › linux-escape-character
Linux Escape Characters: A Comprehensive Guide — linuxvox.com
Let's take a look at a simple example. In the shell, the $ character is used to reference variables. But if you want to print the $ character itself, you can escape it: ... The output of this command will be a single $ character.
🌐
Linux Man Pages
man7.org › linux › man-pages › man4 › console_codes.4.html
console_codes(4) - Linux manual page
VT100-like DC1/DC3 processing may be enabled by the terminal driver. The xterm(1) program (in VT100 mode) recognizes the control characters BEL, BS, HT, LF, VT, FF, CR, SO, SI, ESC. Escape sequences VT100 console sequences not implemented on the Linux console: ESC N SS2 Single shift 2.