feat(shell): use coreutils from u-root

Andrey Nering created

Change summary

go.mod                      |  3 +
go.sum                      |  5 ++
internal/shell/coreutils.go | 59 +++++++++++++++++++++++++++++++++++++++
internal/shell/shell.go     |  2 
4 files changed, 66 insertions(+), 3 deletions(-)

Detailed changes

go.mod 🔗

@@ -39,6 +39,7 @@ require (
 	github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
 	github.com/stretchr/testify v1.10.0
 	github.com/tidwall/sjson v1.2.5
+	github.com/u-root/u-root v0.14.1-0.20250722142936-bf4e78a90dfc
 	github.com/zeebo/xxh3 v1.0.2
 	golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
@@ -108,7 +109,7 @@ require (
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
 	github.com/sethvargo/go-retry v0.3.0 // indirect
 	github.com/spf13/cast v1.7.1 // indirect
-	github.com/spf13/pflag v1.0.6 // indirect
+	github.com/spf13/pflag v1.0.7 // indirect
 	github.com/tetratelabs/wazero v1.9.0 // indirect
 	github.com/tidwall/gjson v1.18.0 // indirect
 	github.com/tidwall/match v1.1.1 // indirect

go.sum 🔗

@@ -234,8 +234,9 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
 github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
+github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
 github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
@@ -258,6 +259,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+github.com/u-root/u-root v0.14.1-0.20250722142936-bf4e78a90dfc h1:HjI/UCF4dRyzizePQrhGUSQvuU7z4tOqMqz6GRGlFCM=
+github.com/u-root/u-root v0.14.1-0.20250722142936-bf4e78a90dfc/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc=
 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
 github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=

internal/shell/coreutils.go 🔗

@@ -0,0 +1,59 @@
+package shell
+
+import (
+	"context"
+
+	"github.com/u-root/u-root/pkg/core"
+	"github.com/u-root/u-root/pkg/core/cat"
+	"github.com/u-root/u-root/pkg/core/chmod"
+	"github.com/u-root/u-root/pkg/core/cp"
+	"github.com/u-root/u-root/pkg/core/find"
+	"github.com/u-root/u-root/pkg/core/ls"
+	"github.com/u-root/u-root/pkg/core/mkdir"
+	"github.com/u-root/u-root/pkg/core/mv"
+	"github.com/u-root/u-root/pkg/core/rm"
+	"github.com/u-root/u-root/pkg/core/touch"
+	"github.com/u-root/u-root/pkg/core/xargs"
+	"mvdan.cc/sh/v3/interp"
+)
+
+var coreUtils = map[string]func() core.Command{
+	"cat":   func() core.Command { return cat.New() },
+	"chmod": func() core.Command { return chmod.New() },
+	"cp":    func() core.Command { return cp.New() },
+	"find":  func() core.Command { return find.New() },
+	"ls":    func() core.Command { return ls.New() },
+	"mkdir": func() core.Command { return mkdir.New() },
+	"mv":    func() core.Command { return mv.New() },
+	"rm":    func() core.Command { return rm.New() },
+	"touch": func() core.Command { return touch.New() },
+	"xargs": func() core.Command { return xargs.New() },
+}
+
+func (s *Shell) coreUtilsHandler() func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
+	return func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
+		return func(ctx context.Context, args []string) error {
+			if len(args) == 0 {
+				return next(ctx, args)
+			}
+
+			program, programArgs := args[0], args[1:]
+
+			newCoreUtil, ok := coreUtils[program]
+			if !ok {
+				return next(ctx, args)
+			}
+
+			c := interp.HandlerCtx(ctx)
+
+			cmd := newCoreUtil()
+			cmd.SetIO(c.Stdin, c.Stdout, c.Stderr)
+			cmd.SetWorkingDir(c.Dir)
+			cmd.SetLookupEnv(func(key string) (string, bool) {
+				v := c.Env.Get(key)
+				return v.Str, v.Set
+			})
+			return cmd.RunContext(ctx, programArgs...)
+		}
+	}
+}

internal/shell/shell.go 🔗

@@ -221,7 +221,7 @@ func (s *Shell) execPOSIX(ctx context.Context, command string) (string, string,
 		interp.Interactive(false),
 		interp.Env(expand.ListEnviron(s.env...)),
 		interp.Dir(s.cwd),
-		interp.ExecHandlers(s.blockHandler()),
+		interp.ExecHandlers(s.blockHandler(), s.coreUtilsHandler()),
 	)
 	if err != nil {
 		return "", "", fmt.Errorf("could not run command: %w", err)