'it'\''s' is how to write it’s in single quotes in Bash or any POSIX shell. Nothing is special between single quotes, not even a backslash, so a single quote can’t appear there at all. Instead, end the quoted part, add an escaped quote \', and start a new quoted part; the shell joins the three pieces into one word:
echo 'it'\''s'
# it's
The tool below writes the same 'it'\''s' for it's. Give it longer text and it quotes the whole thing: it's $HOME becomes 'it'\''s $HOME', with $HOME left unexpanded.
A backslash inside the quotes doesn’t work. Bash reads on to the end of the script looking for the closing quote:
echo 'it\'s'
# b2.sh: line 1: unexpected EOF while looking for matching `''
Double quotes take a single quote as it is, "it's", but inside them $, backticks and \ are still special, so they’re safe only for text you wrote yourself.
$‘…’ quoting
Bash, ksh and zsh also have ANSI-C quoting, $'…', where backslash escapes work as in C, \' included:
echo $'it\'s\tdone'
# it's done
POSIX added it in its 2024 edition, so an older /bin/sh may not have it.
Let the shell quote it
When the text is in a variable, Bash can quote it for you. printf %q puts a backslash before each special character, and ${var@Q}, from Bash 4.4, wraps the value in single quotes:
msg="it's \$HOME"
printf '%q\n' "$msg"
echo "${msg@Q}"
it\'s\ \$HOME
'it'\''s $HOME'
/bin/bash on macOS is 3.2, where ${msg@Q} fails with bad substitution; printf %q works there. The tool’s Unescape reads both forms back to it's $HOME.
This matters when a command string goes through a second shell, as with bash -c, ssh host or eval. The outer shell removes one layer of quoting, so the inner one sees the bare text:
msg="it's \$HOME"
bash -c "echo $msg"
# bash: -c: line 1: unexpected EOF while looking for matching `''
bash -c "echo $(printf '%q' "$msg")"
# it's $HOME
Python’s shlex.quote
From Python, shlex.quote quotes for any POSIX shell. It closes and reopens too, but puts the quote in double quotes instead of escaping it:
import shlex, subprocess
msg = "it's $HOME"
print(shlex.quote(msg))
print(shlex.join(["echo", msg]))
subprocess.run(f"echo {shlex.quote(msg)}", shell=True)
subprocess.run(["echo", msg])
'it'"'"'s $HOME'
echo 'it'"'"'s $HOME'
it's $HOME
it's $HOME
The last call needs no quoting at all: a list of arguments skips the shell, which makes it the safer default. shlex.join needs Python 3.8 or later.