# Stuck with #SX3

**URL:** <https://forum.codecrafters.io/t/stuck-with-sx3/16025>\
**Category:** Challenges\
**Tags:** challenge:shell\
**Created:** [April 5, 2026, 12:05pm UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025 "2026-04-05T12:05:49Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![lakshyabatman](https://yyz1.discourse-cdn.com/flex003/user_avatar/forum.codecrafters.io/lakshyabatman/32/23894_2.png) [@lakshyabatman](https://forum.codecrafters.io/u/lakshyabatman)\
**Post date:** [April 5, 2026, 12:05pm UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025/1 "2026-04-05T12:05:49Z")

</div>

I’m stuck on Stage #SX3

Im not sure why but but in local my implementation works but test fails.

```auto
\[tester::#SX3\] Running tests for Stage #SX3 (History Persistence - Append history to file)
\[tester::#SX3\] Original history file content:
\[tester::#SX3\] \[/tmp/pear.txt\] echo grape strawberry orange
\[tester::#SX3\] \[/tmp/pear.txt\] echo pineapple apple
\[tester::#SX3\] \[/tmp/pear.txt\]
\[tester::#SX3\] Running ./your_program.sh
\[your-program\] $ echo strawberry blueberry pear
\[your-program\] strawberry blueberry pear
\[tester::#SX3\] ✓ Ran echo strawberry blueberry pear
\[your-program\] $ echo apple pear strawberry
\[your-program\] apple pear strawberry
\[tester::#SX3\] ✓ Ran echo apple pear strawberry
\[your-program\] $ echo strawberry grape pineapple
\[your-program\] strawberry grape pineapple
\[tester::#SX3\] ✓ Ran echo strawberry grape pineapple
\[your-program\] $ history -a /tmp/pear.txt
\[tester::#SX3\] Reading contents from /tmp/pear.txt
\[tester::#SX3\] \[/tmp/pear.txt\] echo grape strawberry orange
\[tester::#SX3\] \[/tmp/pear.txt\] echo pineapple apple
\[tester::#SX3\] \[/tmp/pear.txt\]
\[tester::#SX3\] \[/tmp/pear.txt\]
\[tester::#SX3\] ✓ Found command “echo grape strawberry orange” in /tmp/pear.txt
\[tester::#SX3\] ✓ Found command “echo pineapple apple” in /tmp/pear.txt
\[tester::#SX3\] expected command “echo strawberry blueberry pear” at line 3, got “”
\[tester::#SX3\] Test failed

```

```auto
package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"slices"
	"strconv"
	"strings"
	"sync"

	"github.com/chzyer/readline"
	"github.com/codecrafters-io/shell-starter-go/app/bellcompleter"
)

var _ = fmt.Print

var buildInCommands = []string{"echo", "exit", "type", "pwd", "history"}
var dividerCommands = []string{">", "1>", "2>", ">>", "1>>", "2>>"}
var history string = ".shell_history/" + strconv.Itoa(os.Getpid())
var historyAppendOffset int = 0

func main() {
	if os.Getenv("HISTFILE") != "" {
		history = os.Getenv("HISTFILE")
	} else {
		f, e := os.Create(history)
		if e != nil {
			panic(e)
		}
		defer f.Close()
	}
	prefixCompleter := readline.NewPrefixCompleter(
		readline.PcItem("echo"),
		readline.PcItem("exit"),
	)
	completer := &bellcompleter.BellCompleter{Completer: prefixCompleter, TabCount: 0}
	cfg := &readline.Config{
		Prompt: "$ ",
		AutoComplete: completer,
		InterruptPrompt: "^C",
		EOFPrompt: "exit",
		HistoryFile: history,
		DisableAutoSaveHistory: false,
	}

	// Initialize the readline instance.
	rl, err := readline.NewEx(cfg)
	if err != nil {
		panic(err)
	}
	defer rl.Close() // Ensure the terminal is restored to its original state on exit

	for {

		if err != nil {
			panic("input failed")
		}
		line, err := rl.Readline()
		tokens := parseCommand(line)
		commands := make([][]string, 1)
		i := 0
		for _, token := range tokens {
			if token == "|" {
				// fmt.Println("Ci")
				commands = append(commands, []string{})
				i++
			} else {
				commands[i] = append(commands[i], token)
			}

		}
		if err != nil {
			panic("Failed!")
		}
		var wg sync.WaitGroup
		pipeReaders := make([]*io.PipeReader, len(commands))
		pipeWriters := make([]*io.PipeWriter, len(commands))
		for i := 0; i < len(commands)-1; i++ {
			pipeReaders[i+1], pipeWriters[i] = io.Pipe()
		}
		for i, command := range commands {
			if command[0] == "exit" {
				os.Exit(0)
			}
			wg.Add(1)
			go func(cmd []string, idx int) {
				if pipeReaders[idx] != nil {
					defer pipeReaders[idx].Close()
				}
				if pipeWriters[idx] != nil {
					defer pipeWriters[idx].Close()
				}
				executeCommand(cmd, pipeWriters[idx], pipeReaders[idx], rl)
				defer wg.Done()

			}(command, i)
		}
		wg.Wait()

	}
}

func executeCommand(command []string, pipeWriter *io.PipeWriter, pipeReader *io.PipeReader, rl *readline.Instance) {
	execTokens, outStd, redirectionType, _ := extractPipelineCommands(command)

	stdin, stdout, stderr := getIOs(pipeReader, pipeWriter, redirectionType, outStd)

	var commandError error
	if command[0] == "echo" {
		fmt.Fprintln(stdout, strings.Join(execTokens[1:], " "))
	} else if command[0] == "type" {
		fmt.Fprintln(stdout, handleTypeCommand(command))
	} else if command[0] == "pwd" {
		currentPath, _ := os.Getwd()
		fmt.Fprintln(stdout, currentPath)
	} else if command[0] == "cd" {
		pathToGo := command[1]
		if command[1] == "~" {
			pathToGo = os.Getenv("HOME")
		}
		if err := os.Chdir(pathToGo); err != nil {
			fmt.Fprintf(stderr, "cd: %s: No such file or directory\n", pathToGo)
		}
	} else if command[0] == "history" {
		if len(execTokens) > 1 {

			switch execTokens[1] {
			case "-r":
				appenedFileIntoHistory(execTokens[2], rl)
				return
			case "-w":
				appendHistoryIntoFile(execTokens[2])
			case "-a":
				createNewHistory(execTokens[2])
			}
		}
		f, _ := os.ReadFile(history)
		lines := strings.Split(strings.TrimRight(string(f), "\n"), "\n")

		linesToPrint := len(lines)
		if len(execTokens) > 1 {
			i, _ := strconv.Atoi(execTokens[1])
			linesToPrint = min(linesToPrint, i)
			// fmt.Println(linesToPrint)
		}
		for i, l := range lines[(len(lines) - linesToPrint):] {
			if l != "" {
				fmt.Fprintf(stdout, " %d %s\n", (i + (len(lines) - linesToPrint) + 1), l)
			}
		}
	} else {
		commandError = executeSingleCommand(execTokens, stdin, stdout, stderr)

	}
	errorOutput := parseError(commandError)
	switch redirectionType {
	case "redirect", "redirectAppend":
		if errorOutput != "" {
			fmt.Println(errorOutput)
		}
	case "redirectError", "redirectAppendError":
		if stdout != nil {

		}
	}
}

func appenedFileIntoHistory(input string, rl *readline.Instance) {
	historyFile, _ := os.ReadFile(input)
	lines := strings.Split(strings.TrimRight(string(historyFile), "\n"), "\n")
	for _, line := range lines {
		rl.SaveHistory(line)
	}
}

func appendHistoryIntoFile(output string) {
	file, _ := os.Create(output)
	historyData, _ := os.ReadFile(history)
	file.Write(historyData)
}

func createNewHistory(output string) {
	historyData, e := os.ReadFile(history)
	if e != nil {
		panic(e)
	}
	lines := strings.Split(strings.TrimRight(string(historyData), "\n"), "\n")

	existingFileLines, _ := os.ReadFile(output)
	newLines := strings.Split(strings.Trim(string(existingFileLines), "\n"), "\n")
	newLines = append(newLines, lines[historyAppendOffset:]...)
	historyAppendOffset = len(lines)
	file, e := os.OpenFile(output, os.O_CREATE|os.O_WRONLY, 0644)
	if e != nil {
		panic(e)
	}
	defer file.Close()
	for _, line := range newLines {
		file.WriteString(line + "\n")
	}
}

func statOrZero(f *os.File) int64 {
	if fi, err := f.Stat(); err == nil {
		return fi.Size()
	}
	return 0
}

func getIOs(pipeReader *io.PipeReader, pipeWriter *io.PipeWriter, redirectionType string, outStd string) (io.Reader, io.Writer, io.Writer) {
	var stdin io.Reader
	if pipeReader != nil {
		stdin = pipeReader
	}
	var stdout io.Writer
	var stderr io.Writer
	stdout = os.Stdout
	stderr = os.Stderr
	if pipeWriter != nil {
		stdout = pipeWriter
	} else {
		switch redirectionType {
		case "redirect":
			f, _ := os.OpenFile(outStd, os.O_CREATE|os.O_WRONLY, 0644)
			stdout = f
		case "redirectAppend":
			f, _ := os.OpenFile(outStd, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
			stdout = f
		case "redirectError":
			f, _ := os.OpenFile(outStd, os.O_CREATE|os.O_WRONLY, 0644)
			stderr = f
		case "redirectAppendError":
			f, _ := os.OpenFile(outStd, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
			stderr = f
		}
	}
	return stdin, stdout, stderr

}

func executeSingleCommand(command []string, pipeReader io.Reader, pipeWriter io.Writer, pipeErr io.Writer) error {

	path := checkAndGetInPaths(command[0], strings.Split(os.Getenv("PATH"), ":"))
	if path == "" {
		fmt.Fprintln(os.Stderr, command[0]+": not found")
		return nil
	} else {
		var cmd *exec.Cmd
		if len(command) == 1 {
			cmd = exec.Command(command[0])
		} else {
			cmd = exec.Command(command[0], command[1:]...)
		}
		cmd.Stdin = pipeReader
		cmd.Stdout = pipeWriter
		cmd.Stderr = pipeErr
		return cmd.Run()
	}

}

func handleTypeCommand(command []string) string {
	if slices.Contains(buildInCommands, command[1]) {
		return command[1] + " is a shell builtin"
	} else {
		path := checkAndGetInPaths(command[1], strings.Split(os.Getenv("PATH"), ":"))
		if path == "" {
			return command[1] + ": not found"
		} else {
			return command[1] + " is " + path
		}
	}
}

func checkAndGetInPaths(command string, paths []string) string {
	for _, path := range paths {
		foundPath, err := exec.LookPath(path + "/" + command)
		if err == nil {
			return foundPath
		}
	}
	return ""
}

func parseCommand(line string) []string {

	var res []string
	var currentWord strings.Builder
	isSingleQuotes := false
	isDoubleQuotes := false
	isEscapeMode := false
	for _, ch := range strings.Trim(line, "\n") {
		// fmt.P/rintln(res)
		switch {
		case isEscapeMode:
			isEscapeMode = false
			currentWord.WriteRune(ch)
		case ch == '\\' && !isSingleQuotes:
			isEscapeMode = true
		case ch == '\'' && !isDoubleQuotes:
			isSingleQuotes = !isSingleQuotes

		case ch == '"' && !isSingleQuotes:
			isDoubleQuotes = !isDoubleQuotes
		case ch == ' ' && !isSingleQuotes && !isDoubleQuotes:
			if currentWord.Len() > 0 {
				res = append(res, currentWord.String())
				currentWord.Reset()
			}
		default:
			currentWord.WriteRune(ch)
		}
	}
	if currentWord.Len() > 0 {
		res = append(res, currentWord.String())
	}
	// fmt.Println(res)
	return res
}

func extractPipelineCommands(tokens []string) ([]string, string, string, bool) {
	for i, t := range tokens {
		if !slices.Contains(dividerCommands, t) {
			continue
		}
		if i+1 >= len(tokens) {
			return nil, "", "redirect", false
		}
		var commandType string
		switch {
		case t == ">" || t == "1>":
			commandType = "redirect"
		case t == "2>":
			commandType = "redirectError"

		case t == ">>" || t == "1>>":
			commandType = "redirectAppend"

		case t == "2>>":
			commandType = "redirectAppendError"
		}
		return tokens[:i], strings.Join(tokens[i+1:], " "), commandType, true

	}
	return tokens, "", "none", true
}

func parseError(err error) string {
	if err == nil {
		return ""
	}
	if exitErr, ok := err.(*exec.ExitError); ok {
		return strings.Trim(string(exitErr.Stderr), "\n")
	}
	return fmt.Sprintf("%v", err)
}

```

Please ignore the terrible code, im kind of new to golang

---

<div class="post-metadata">

**Author:** ![lakshyabatman](https://yyz1.discourse-cdn.com/flex003/user_avatar/forum.codecrafters.io/lakshyabatman/32/23894_2.png) [@lakshyabatman](https://forum.codecrafters.io/u/lakshyabatman)\
**Post date:** [April 5, 2026, 12:06pm UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025/2 "2026-04-05T12:06:23Z")

</div>

Not sure why history is not being added in the file but it clearly does in local.

---

<div class="post-metadata">

**Author:** ![lakshyabatman](https://yyz1.discourse-cdn.com/flex003/user_avatar/forum.codecrafters.io/lakshyabatman/32/23894_2.png) [@lakshyabatman](https://forum.codecrafters.io/u/lakshyabatman)\
**Post date:** [April 5, 2026, 12:10pm UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025/3 "2026-04-05T12:10:10Z")

</div>

![Screenshot 2026-04-05 at 5.39.55 PM](https://canada1.discourse-cdn.com/flex003/uploads/codecrafters/original/3X/0/c/0c9e13daa2c8a22bea5d6112987820083fac5257.png)

---

<div class="post-metadata">

**Author:** ![andy1li](https://yyz1.discourse-cdn.com/flex003/user_avatar/forum.codecrafters.io/andy1li/32/10_2.png) [@andy1li](https://forum.codecrafters.io/u/andy1li)\
**Post date:** [April 8, 2026, 10:36am UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025/5 "2026-04-08T10:36:59Z")

</div>

Hey @lakshyabatman, looks like you’ve got past this stage. Do you happen to remember what was wrong? Would love to see if we can improve the tester / instructions.

---

<div class="post-metadata">

**Author:** ![andy1li](https://yyz1.discourse-cdn.com/flex003/user_avatar/forum.codecrafters.io/andy1li/32/10_2.png) [@andy1li](https://forum.codecrafters.io/u/andy1li)\
**Post date:** [April 13, 2026, 9:11pm UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025/7 "2026-04-13T21:11:32Z")

</div>

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex003/uploads/codecrafters/original/3X/7/0/700657133935c15703e22c7c8870394f6e6dc27d.svg) [@system](https://forum.codecrafters.io/u/system)\
**Post date:** [April 18, 2026, 9:12pm UTC](https://forum.codecrafters.io/t/stuck-with-sx3/16025/8 "2026-04-18T21:12:22Z")

</div>

Closed on April 18, 2026, 9:12pm UTC

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.
