In my understanding of command substitution this should work, but it doesn't, can you explain me why and how to do something like this.
Why does this work:
cd ..; echo 123 # output is "123", after changing directories
...when this doesn't:
cd $(echo "..; echo 123") # error message is "cd: too many arguments"
Command substitution results (like expansion of variables) do not go through all parsing phases; they only go through word-splitting[1] and glob expansion[2], and even those happen only when the expansion itself is unquoted.
That means that your result is identical to:
cd "..;" "echo" "123"
...the semicolon is treated as literal text to be passed to the cd
command, not shell syntax.
This is a feature, not a bug: If command substitution results were parsed as syntax, writing secure shell scripts handling untrusted data would be basically impossible.
[1] dividing the results into "words" on characters in IFS
-- or, by default, whitespace.
[2] looking at whether each resulting word can be treated as a filename-matching pattern, and, if so, matching them against the local filesystem.