В дополнение к другим имеющимся у вас ответам, есть несколько других случаев, когда foo/ и foo/* могут отличаться (не только файлы, начинающиеся с .). Кроме того, если вы хотите иметь файлы, которые начинаются с . в соответствии с вашим глобусом, вы также можете включить это с shopt -s dotglob .
Во-первых, если в foo/ нет записей и вы не включили nullglob то foo/* будет возвращен как литерал, передаваемый в cp . Поскольку (в данном случае) нет источника, foo/* cp будет жаловаться, в то время как cp -R foo/ всегда будет иметь по крайней мере foo/ для копирования. Если бы вы nullglob то foo/* расширился бы до нуля, так что в итоге вы пропустили бы аргумент cp .
Другой случай, который нужно рассмотреть, - это если в foo/ записей. Оболочка расширяет глоб и затем вызывает процесс, но если глоб расширяется до слишком большого количества аргументов, вы получите ошибку. С cp -R foo/ вас есть только 2 аргумента (хотя, вероятно, у вас будет цель где-то еще).