If I understand it right $ is working like parentheses.
No. ($) :: (a -> b) -> a -> b is not some special syntax, it is just an operator like (+), (-) or operators you define yourself.
The reason that this works is because of the infixr 0 precedence, it thus means that this operator has the lowest precedence, and thus will group elements on the left on the right even if they contain operators, because the precedence will be higher.
($) itself is implemented as:
infixr 0 $
($) :: (a -> b) -> a -> b
($) f x = f x
it is thus in essence simple function application, but the operator precedence let it look as if there are hidden parenthesis for the left and right operand.
The problem with your expression is that it contains now two operators after each other, indeed:
myButLast l = l !! $ length l-2
so that means that the parser got confused.
You can use operator sectioning and construct a function (l !!) and then use the operator:
myButLast l = (l !!) $ length l-2
-- ↑ operator sectioning
But it is probably better not to use length here in the first place: by using length you will iterate twice over the list: first to determine the list, and then to obtain the one-but-last index. This is inefficient, and likely will also result in large amounts of memory used.
You can simplify this with:
myButLast :: [a] -> a
myButLast (x:x2:xs) = go xs x x2
where go [] y _ = y
go (y2:ys) _ y = go ys y y2
myButLast _ = error "empty list"