Why does bash link to ncurses?

If you run bash as:

LD_DEBUG=bindings bash

on a GNU system, and grep for bash.*tinfo in that output, you'll see something like:

   797:     binding file bash [0] to /lib/x86_64-linux-gnu/libtinfo.so.5 [0]: normal symbol `UP'
   797:     binding file bash [0] to /lib/x86_64-linux-gnu/libtinfo.so.5 [0]: normal symbol `PC'
   797:     binding file bash [0] to /lib/x86_64-linux-gnu/libtinfo.so.5 [0]: normal symbol `BC'
   797:     binding file bash [0] to /lib/x86_64-linux-gnu/libtinfo.so.5 [0]: normal symbol `tgetent'
   797:     binding file bash [0] to /lib/x86_64-linux-gnu/libtinfo.so.5 [0]: normal symbol `tgetstr'
   797:     binding file bash [0] to /lib/x86_64-linux-gnu/libtinfo.so.5 [0]: normal symbol `tgetflag'

You can confirm from the output of nm -D /bin/bash that bash is using those symbols from tinfo.

Bringing the man page for any of those symbols clarifies what they're for:

$ man tgetent
   PC, UP, BC, ospeed, tgetent, tgetflag, tgetnum, tgetstr, tgoto, tputs -
   direct curses interface to the terminfo capability database

Basically, bash, more likely its readline (libreadline is statically linked in) editor, uses those to query the terminfo database to find out about terminal capabilities so it can run its line editor properly (sending the right escape sequences and identify key presses correctly) on any terminal.

As to why readline is statically linked into bash, you have to bear in mind that readline is developed alongside bash by the same person and is included in the source of bash.

It is possible to build bash to be linked with the system's installed libreadline, but only if that one is of a compatible version, and that's not the default. You need to call the configure script at compilation time with --with-installed-readline.

bash is a termcap application via readline, like screen and some other programs. On most Linux-based systems (aside from Slackware), you're likely to see ncurses as underlying implementation of termcap.

The manual page for tgetent (named curs_termcap because that's the way it was done in SVr4...) says:

These routines are included as a conversion aid for programs that use the termcap library. Their parameters are the same and the routines are emulated using the terminfo database. Thus, they can only be used to query the capabilities of entries for which a terminfo entry has been compiled.

That is, if the calling program does not look closely at the data returned, and uses the conventional termcap interface for reading the terminal description and writing data to the screen, it works just like the original termcap.

Most termcap applications don't look that closely (xterm is a rare exception — see FAQ). So bash works with ncurses.

However, the termcap library is smaller than ncurses. Quite a while ago that mattered, and since 1997 ncurses has had a configure option --with-termlib which makes it build the termcap- and terminfo-specific parts as a library separate from the functions needed in the higher-level curses library. A few years passed, and some of the Linux-based distributions incorporated that into their packages.

Since bash doesn't use any of the curses functions (libncurses, etc.), it's reasonable to link only against the libtinfo.

readline is the termcap-specific part of bash (actually when I first encountered bash, its termcap parts were hardcoded, even though the official source used termcap — perhaps to save a few more bytes). When bash is built with the bundled readline, you will not see readline as a separate library because there would be no point in making that bundled readline install as a (possibly conflicting) shared library. But (depending on your system), you may see libtinfo because ncurses is built one way or the other (split or not) — not both.