diff --git a/script/cmds.go b/script/cmds.go index a72abb2..8c74912 100644 --- a/script/cmds.go +++ b/script/cmds.go @@ -44,6 +44,7 @@ func DefaultCmds() map[string]Cmd { "mv": Mv(), "rm": Rm(), "replace": Replace(), + "sed": Sed(), "sleep": Sleep(), "stderr": Stderr(), "stdout": Stdout(), @@ -882,6 +883,45 @@ func Replace() Cmd { }) } +// Sed implements a simple regexp replacement of text in a file. +func Sed() Cmd { + return Command( + CmdUsage{ + Summary: "substitute strings in a file with a regexp", + Args: "regexp replacement file", + Detail: []string{ + "A simple sed-like command for replacing text matching regular expressions", + "in a file.", + "", + "Implemented using regexp.ReplaceAll, see its docs for what is supported:", + "https://pkg.go.dev/regexp#Regexp.ReplaceAll", + }, + RegexpArgs: firstNonFlag, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) != 3 { + return nil, ErrUsage + } + + re, err := regexp.Compile(args[0]) + if err != nil { + return nil, err + } + replacement := args[1] + file := s.Path(args[2]) + + data, err := os.ReadFile(file) + if err != nil { + return nil, err + } + lines := strings.Split(string(data), "\n") + for i, line := range lines { + lines[i] = re.ReplaceAllString(line, replacement) + } + return nil, os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0666) + }) +} + // Rm removes a file or directory. // // If a directory, Rm also recursively removes that directory's diff --git a/script/scripttest/testdata/sed.txt b/script/scripttest/testdata/sed.txt new file mode 100644 index 0000000..4c33b5e --- /dev/null +++ b/script/scripttest/testdata/sed.txt @@ -0,0 +1,20 @@ +# Tests for the sed command + +sed '' '' input.txt +sed notfound unexpected input.txt +sed \d+ N input.txt +sed [uz] '' input.txt +sed a(x*)b 'a${1}c' input.txt +sed ^(s+)$ '${1}${1}' input.txt +cmp input.txt expected.txt + +-- input.txt -- +foo123bar +quuxbaz123 +axxxxxxb +sss +-- expected.txt -- +fooNbar +qxbaN +axxxxxxc +ssssss