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:
$ var=$'Hello\nGood world.\n'
$ echo "$var"
Hello
Good world.
$ echo "${var@Q}"
$'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 "$var"
$'Hello world\n'
Quick check with all (128) ASCII bytes:
Note that all bytes from 128 to 255 have to be escaped.
for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column
This must render something like:
00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m
06 E $'\006'    20 E \          3A - :          54 - T          6E - n
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x
11 E $'\021'    2B - +          45 - E          5F - _          79 - y
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f
19 E $'\031'    33 - 3          4D - M          67 - g
Where first field is hexadecimal value of byte, second contain E if character need to be escaped and third field show escaped presentation of character.
Small script 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.
quickListOfSpecialCharsFromString() {
    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[*]}"
}
quickListOfSpecialCharsFromString $'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
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 sometime:
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