Shells have also been built to help glue applications together, and are more concise for the easy versions than most programming languages, sometimes heavily so. Most programming languages, (outside of maybe perl and ruby), don't make this use case anywhere close to their top priority, but shells do, trading off other things like a simple syntax for bitwise OR.
In another sense, shells are exactly what you describe, it just happens that the most popular languages for them happen to be sh/bash/zsh/fish etc, which make a different set of tradeoffs than the 'traditional' programming languages.
I'm only partially aware of the history standpoint. My understanding is that before awk, shells (sh, mostly, IIRC) were about the only scripting level languages on various Unixen.
This all cascades to make a language that looks much different than you would want to write a full application in. One of the primary reasons is because of how shells need to handle strings. It is much more convenient to not type out quotes around string literals and it is nice to have syntax to reference environment variables, files, previous commands, etc. This leads to extremely complex quoting and expansion rules in most shells. It is not intuitive, but once learned it makes for a great interactive experience (compared with traditional languages).
Previously discussed two years ago here: (https://news.ycombinator.com/item?id=9207738)
The language has evolved with compatibility since the original release in 1971, before it had loops, conditionals, or functions. That's before yacc and probably before some common parsing algorithms were even invented. It is a proper programming language by now, but it just feels old and crufty.
I think it needs a few more OS bindings as well. The "test -f / -d" stuff is a little impoverished.
Why are terminals NOT just REPLs for regular programming languages with OS_Convenience_Functions #include'd automatically?