diff --git a/common/support.go b/common/support.go
index 3348b996e91f1e34283c67c87c1af19e1176d735..3daf8f58a7c20982412d4bb737910dcd3f2b2967 100644
--- a/common/support.go
+++ b/common/support.go
@@ -37,7 +37,16 @@ func GetRemoteLongRunningBuild() (JobResponse, error) {
 	return getRemoteBuildResponse("sleep 3600")
 }
 
-func getRemoteBuildResponse(commands string) (response JobResponse, err error) {
+func GetMultilineBashBuild() (JobResponse, error) {
+	return getRemoteBuildResponse(`if true; then
+	bash \
+		--login \
+		-c 'echo Hello World'
+fi
+`)
+}
+
+func getRemoteBuildResponse(commands ...string) (response JobResponse, err error) {
 	response = JobResponse{
 		GitInfo: GitInfo{
 			RepoURL:   repoRemoteURL,
@@ -49,7 +58,7 @@ func getRemoteBuildResponse(commands string) (response JobResponse, err error) {
 		Steps: Steps{
 			Step{
 				Name:         StepNameScript,
-				Script:       StepScript{commands},
+				Script:       commands,
 				When:         StepWhenAlways,
 				AllowFailure: false,
 			},
@@ -59,7 +68,7 @@ func getRemoteBuildResponse(commands string) (response JobResponse, err error) {
 	return
 }
 
-func getLocalBuildResponse(commands string) (response JobResponse, err error) {
+func getLocalBuildResponse(commands ...string) (response JobResponse, err error) {
 	localRepoURL, err := getLocalRepoURL()
 	if err != nil {
 		return
@@ -76,7 +85,7 @@ func getLocalBuildResponse(commands string) (response JobResponse, err error) {
 		Steps: Steps{
 			Step{
 				Name:         StepNameScript,
-				Script:       StepScript{commands},
+				Script:       commands,
 				When:         StepWhenAlways,
 				AllowFailure: false,
 			},
diff --git a/executors/shell/executor_shell_test.go b/executors/shell/executor_shell_test.go
index dbdd4e52542b99f64dc0c6eac01547f7cb6e64e9..cb4eeaf2dfcbf58b65da2fe81ef07973d85f462b 100644
--- a/executors/shell/executor_shell_test.go
+++ b/executors/shell/executor_shell_test.go
@@ -442,3 +442,16 @@ func TestBuildWithDebugTrace(t *testing.T) {
 		assert.Regexp(t, `[^$] echo Hello World`, out)
 	})
 }
+func TestBuildMultilineCommand(t *testing.T) {
+	multilineBuild, err := common.GetMultilineBashBuild()
+	assert.NoError(t, err)
+	build, cleanup := newBuild(t, multilineBuild, "bash")
+	defer cleanup()
+
+	// The default build shouldn't have debug tracing enabled
+	out, err := runBuildReturningOutput(t, build)
+	assert.NoError(t, err)
+	assert.NotContains(t, out, "bash")
+	assert.Contains(t, out, "Hello World")
+	assert.Contains(t, out, "collapsed multi-line command")
+}
diff --git a/helpers/gitlab_ci_yaml_parser/parser.go b/helpers/gitlab_ci_yaml_parser/parser.go
index fd994610924f3273b1bf0bd11f3e79ba4f6a2b4d..a99704a5b96903bfd5f830acd428fcfb9cc2475b 100644
--- a/helpers/gitlab_ci_yaml_parser/parser.go
+++ b/helpers/gitlab_ci_yaml_parser/parser.go
@@ -67,15 +67,15 @@ func (c *GitLabCiYamlParser) prepareJobInfo(job *common.JobResponse) (err error)
 
 func (c *GitLabCiYamlParser) getCommands(commands interface{}) (common.StepScript, error) {
 	if lines, ok := commands.([]interface{}); ok {
-		text := ""
+		var steps common.StepScript
 		for _, line := range lines {
 			if lineText, ok := line.(string); ok {
-				text += lineText + "\n"
+				steps = append(steps, lineText)
 			} else {
 				return common.StepScript{}, errors.New("unsupported script")
 			}
 		}
-		return common.StepScript(strings.Split(text, "\n")), nil
+		return steps, nil
 	} else if text, ok := commands.(string); ok {
 		return common.StepScript(strings.Split(text, "\n")), nil
 	} else if commands != nil {
@@ -116,9 +116,7 @@ func (c *GitLabCiYamlParser) prepareSteps(job *common.JobResponse) (err error) {
 	if err != nil {
 		return
 	}
-	for _, scriptLine := range script {
-		scriptCommands = append(scriptCommands, scriptLine)
-	}
+	scriptCommands = append(scriptCommands, script...)
 
 	afterScriptCommands, err = c.getCommands(c.jobConfig["after_script"])
 	if err != nil {
diff --git a/shells/abstract.go b/shells/abstract.go
index bacdd3292d31ab342af56c11b667478014b2b61d..d079540b1355c4c9d6ce41caaf09f5af656230e3 100644
--- a/shells/abstract.go
+++ b/shells/abstract.go
@@ -339,12 +339,17 @@ func (b *AbstractShell) writeDownloadArtifactsScript(w ShellWriter, info common.
 }
 
 // Write the given string of commands using the provided ShellWriter object.
-func (b *AbstractShell) writeCommands(w ShellWriter, commands string) {
-	commands = strings.TrimSpace(commands)
-	for _, command := range strings.Split(commands, "\n") {
+func (b *AbstractShell) writeCommands(w ShellWriter, commands ...string) {
+	for _, command := range commands {
 		command = strings.TrimSpace(command)
 		if command != "" {
-			w.Notice("$ %s", command)
+			lines := strings.SplitN(command, "\n", 2)
+			if len(lines) > 1 {
+				// TODO: this should be collapsable once we introduce that in GitLab
+				w.Notice("$ %s # collapsed multi-line command", lines[0])
+			} else {
+				w.Notice("$ %s", lines[0])
+			}
 		} else {
 			w.EmptyLine()
 		}
@@ -373,8 +378,7 @@ func (b *AbstractShell) writeUserScript(w ShellWriter, info common.ShellScriptIn
 		b.writeCommands(w, info.PreBuildScript)
 	}
 
-	commands := strings.Join(scriptStep.Script, "\n")
-	b.writeCommands(w, commands)
+	b.writeCommands(w, scriptStep.Script...)
 
 	if info.PostBuildScript != "" {
 		b.writeCommands(w, info.PostBuildScript)
@@ -503,18 +507,7 @@ func (b *AbstractShell) writeAfterScript(w ShellWriter, info common.ShellScriptI
 	b.writeCdBuildDir(w, info)
 
 	w.Notice("Running after script...")
-
-	for _, command := range afterScriptStep.Script {
-		command = strings.TrimSpace(command)
-		if command != "" {
-			w.Notice("$ %s", command)
-		} else {
-			w.EmptyLine()
-		}
-		w.Line(command)
-		w.CheckForErrors()
-	}
-
+	b.writeCommands(w, afterScriptStep.Script...)
 	return nil
 }