• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yyle88 / osexec / 14537258186

18 Apr 2025 03:10PM UTC coverage: 40.65% (-1.9%) from 42.517%
14537258186

push

github

yangyile
再添加个基本操作

3 of 35 new or added lines in 2 files covered. (8.57%)

1 existing line in 1 file now uncovered.

250 of 615 relevant lines covered (40.65%)

4.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.56
/command.go
1
package osexec
2

3
import (
4
        "bufio"
5
        "io"
6
        "os"
7
        "os/exec"
8
        "slices"
9
        "strings"
10
        "sync"
11

12
        "github.com/yyle88/done"
13
        "github.com/yyle88/erero"
14
        "github.com/yyle88/eroticgo"
15
        "github.com/yyle88/osexec/internal/utils"
16
        "github.com/yyle88/printgo"
17
        "github.com/yyle88/tern"
18
        "github.com/yyle88/zaplog"
19
        "go.uber.org/zap"
20
)
21

22
// CommandConfig represents the configuration for executing shell commands.
23
// CommandConfig 表示执行 shell 命令的配置。
24
type CommandConfig struct {
25
        Envs      []string // Optional environment variables. // 填写可选的环境变量。
26
        Path      string   // Optional execution path. // 填写可选的执行路径。
27
        ShellType string   // Optional type of shell to use, e.g., bash, zsh. // 填写可选的 shell 类型,例如 bash,zsh。
28
        ShellFlag string   // Optional shell flag, e.g., "-c". // 填写可选的 Shell 参数,例如 "-c"。
29
        DebugMode bool     // enable debug mode. // 是否启用调试模式,即打印调试的日志。
30
        MatchPipe func(line string) bool
31
        MatchMore bool
32
        TakeExits map[int]string
33
}
34

35
// NewCommandConfig creates and returns a new CommandConfig instance.
36
// NewCommandConfig 创建并返回一个新的 CommandConfig 实例。
37
func NewCommandConfig() *CommandConfig {
24✔
38
        return &CommandConfig{
24✔
39
                DebugMode: debugModeOpen, // Initial value is consistent with the debugModeOpen variable. // 初始值与 debugModeOpen 变量保持一致。
24✔
40
                TakeExits: make(map[int]string),
24✔
41
        }
24✔
42
}
24✔
43

44
// WithEnvs sets the environment variables for CommandConfig and returns the updated instance.
45
// WithEnvs 设置 CommandConfig 的环境变量并返回更新后的实例。
46
func (c *CommandConfig) WithEnvs(envs []string) *CommandConfig {
8✔
47
        c.Envs = envs
8✔
48
        return c
8✔
49
}
8✔
50

51
// WithPath sets the execution path for CommandConfig and returns the updated instance.
52
// WithPath 设置 CommandConfig 的执行路径并返回更新后的实例。
53
func (c *CommandConfig) WithPath(path string) *CommandConfig {
4✔
54
        c.Path = path
4✔
55
        return c
4✔
56
}
4✔
57

58
// WithShellType sets the shell type for CommandConfig and returns the updated instance.
59
// WithShellType 设置 CommandConfig 的 shell 类型并返回更新后的实例。
60
func (c *CommandConfig) WithShellType(shellType string) *CommandConfig {
2✔
61
        c.ShellType = shellType
2✔
62
        return c
2✔
63
}
2✔
64

65
// WithShellFlag sets the shell flag for CommandConfig and returns the updated instance.
66
// WithShellFlag 设置 CommandConfig 的 shell 参数并返回更新后的实例。
67
func (c *CommandConfig) WithShellFlag(shellFlag string) *CommandConfig {
2✔
68
        c.ShellFlag = shellFlag
2✔
69
        return c
2✔
70
}
2✔
71

72
// WithShell sets both the shell type and shell flag for CommandConfig and returns the updated instance.
73
// WithShell 同时设置 CommandConfig 的 shell 类型和 shell 参数,并返回更新后的实例。
74
func (c *CommandConfig) WithShell(shellType, shellFlag string) *CommandConfig {
18✔
75
        c.ShellType = shellType
18✔
76
        c.ShellFlag = shellFlag
18✔
77
        return c
18✔
78
}
18✔
79

80
// WithBash sets the shell to bash with the "-c" flag and returns the updated instance.
81
// WithBash 设置 shell 为 bash 并附带 "-c" 参数,返回更新后的实例。
82
func (c *CommandConfig) WithBash() *CommandConfig {
6✔
83
        return c.WithShell("bash", "-c")
6✔
84
}
6✔
85

86
// WithZsh sets the shell to zsh with the "-c" flag and returns the updated instance.
87
// WithZsh 设置 shell 为 zsh 并附带 "-c" 参数,返回更新后的实例。
88
func (c *CommandConfig) WithZsh() *CommandConfig {
×
89
        return c.WithShell("zsh", "-c")
×
90
}
×
91

92
// WithSh sets the shell to sh with the "-c" flag and returns the updated instance.
93
// WithSh 设置 shell 为 sh 并附带 "-c" 参数,返回更新后的实例。
94
func (c *CommandConfig) WithSh() *CommandConfig {
8✔
95
        return c.WithShell("sh", "-c")
8✔
96
}
8✔
97

98
// WithDebugMode sets the debug mode for CommandConfig and returns the updated instance.
99
// WithDebugMode 设置 CommandConfig 的调试模式并返回更新后的实例。
100
func (c *CommandConfig) WithDebugMode(debugMode bool) *CommandConfig {
10✔
101
        c.DebugMode = debugMode
10✔
102
        return c
10✔
103
}
10✔
104

105
// WithDebug sets the debug mode to true for CommandConfig and returns the updated instance.
106
// WithDebug 将 CommandConfig 的调试模式设置为 true 并返回更新后的实例。
107
func (c *CommandConfig) WithDebug() *CommandConfig {
×
108
        return c.WithDebugMode(true)
×
109
}
×
110

111
// WithMatchPipe sets the match pipe function for CommandConfig and returns the updated instance.
112
// WithMatchPipe 设置 CommandConfig 的匹配管道函数并返回更新后的实例。
113
func (c *CommandConfig) WithMatchPipe(matchPipe func(line string) bool) *CommandConfig {
×
114
        c.MatchPipe = matchPipe
×
115
        return c
×
116
}
×
117

118
// WithMatchMore sets the match more flag for CommandConfig and returns the updated instance.
119
// WithMatchMore 设置 CommandConfig 的匹配更多标志并返回更新后的实例。
120
func (c *CommandConfig) WithMatchMore(matchMore bool) *CommandConfig {
×
121
        c.MatchMore = matchMore
×
122
        return c
×
123
}
×
124

125
// WithTakeExits sets the accepted exit codes for CommandConfig and returns the updated instance.
126
// WithTakeExits 设置 CommandConfig 的接受退出码集合并返回更新后的实例。
127
func (c *CommandConfig) WithTakeExits(takeExits map[int]string) *CommandConfig {
2✔
128
        //这里需要复制 map 避免出问题,其次是不要使用 clone 以免外面传的是 nil 就不好啦
2✔
129
        expMap := make(map[int]string, len(takeExits))
2✔
130
        for k, v := range takeExits {
4✔
131
                expMap[k] = v
2✔
132
        }
2✔
133
        //这里完全覆盖而不是增补,是因为覆盖更符合预期,否则还得写增补逻辑
134
        c.TakeExits = expMap
2✔
135
        return c
2✔
136
}
137

138
// WithExpectExit adds an expected exit code to CommandConfig and returns the updated instance.
139
// WithExpectExit 向 CommandConfig 添加一个期望的退出码并返回更新后的实例。
140
func (c *CommandConfig) WithExpectExit(exitCode int, reason string) *CommandConfig {
2✔
141
        c.TakeExits[exitCode] = reason
2✔
142
        return c
2✔
143
}
2✔
144

145
// WithExpectCode adds an expected exit code to CommandConfig and returns the updated instance.
146
// WithExpectCode 向 CommandConfig 添加一个期望的退出码并返回更新后的实例。
147
func (c *CommandConfig) WithExpectCode(exitCode int) *CommandConfig {
2✔
148
        c.TakeExits[exitCode] = "EXPECTED-EXIT-CODES"
2✔
149
        return c
2✔
150
}
2✔
151

152
// Exec executes a shell command with the specified name and arguments, using the CommandConfig configuration.
153
// Exec 使用 CommandConfig 的配置执行带有指定名称和参数的 shell 命令。
154
func (c *CommandConfig) Exec(name string, args ...string) ([]byte, error) {
22✔
155
        if err := c.checkConfig(name, args); err != nil {
22✔
156
                return nil, erero.Ero(err)
×
157
        }
×
158
        command := c.prepareCommand(name, args)
22✔
159
        return utils.WarpResults(done.VAE(command.CombinedOutput()), c.DebugMode, c.TakeExits)
22✔
160
}
161

162
// ExecWith executes a shell command with the specified name and arguments, using the CommandConfig configuration.
163
// ExecWith 使用 CommandConfig 的配置执行带有指定名称和参数的 shell 命令。
NEW
164
func (c *CommandConfig) ExecWith(name string, args []string, runWith func(command *exec.Cmd)) ([]byte, error) {
×
NEW
165
        if err := c.checkConfig(name, args); err != nil {
×
NEW
166
                return nil, erero.Ero(err)
×
NEW
167
        }
×
NEW
168
        command := c.prepareCommand(name, args)
×
NEW
169
        runWith(command)
×
NEW
170
        return utils.WarpResults(done.VAE(command.CombinedOutput()), c.DebugMode, c.TakeExits)
×
171
}
172

173
func (c *CommandConfig) checkConfig(name string, args []string) error {
24✔
174
        if name == "" {
24✔
175
                return erero.New("can-not-execute-with-empty-command-name")
×
176
        }
×
177
        if c.ShellFlag == "" && c.ShellType == "" {
28✔
178
                if strings.Contains(name, " ") {
4✔
179
                        return erero.New("can-not-contains-space-in-command-name")
×
180
                }
×
181
        }
182
        if c.ShellFlag != "" {
44✔
183
                if c.ShellType == "" {
20✔
184
                        return erero.New("can-not-execute-with-wrong-shell-command")
×
185
                }
×
186
        }
187
        if c.ShellType != "" {
44✔
188
                if c.ShellFlag != "-c" {
20✔
189
                        return erero.New("can-not-execute-with-wrong-shell-options")
×
190
                }
×
191
        }
192
        if c.DebugMode {
46✔
193
                debugMessage := c.makeCommandMessage(name, args)
22✔
194
                utils.ShowCommand(debugMessage)
22✔
195
                zaplog.ZAPS.Skip1.LOG.Debug("EXEC:", zap.String("CMD", debugMessage))
22✔
196
        }
22✔
197
        return nil
24✔
198
}
199

200
func (c *CommandConfig) prepareCommand(name string, args []string) *exec.Cmd {
24✔
201
        cmd := tern.BFF(c.ShellType != "",
24✔
202
                func() *exec.Cmd {
44✔
203
                        return exec.Command(c.ShellType, c.ShellFlag, name+" "+strings.Join(args, " "))
20✔
204
                },
20✔
205
                func() *exec.Cmd {
4✔
206
                        return exec.Command(name, args...)
4✔
207
                })
4✔
208
        cmd.Dir = c.Path
24✔
209
        cmd.Env = tern.BF(len(c.Envs) > 0, func() []string {
32✔
210
                return append(os.Environ(), c.Envs...)
8✔
211
        })
8✔
212
        return cmd
24✔
213
}
214

215
// makeCommandMessage constructs a command-line string based on the CommandConfig and given command name and arguments.
216
// makeCommandMessage 根据 CommandConfig 和指定的命令名称及参数构造命令行字符串。
217
func (c *CommandConfig) makeCommandMessage(name string, args []string) string {
22✔
218
        var pts = printgo.NewPTS()
22✔
219
        if c.Path != "" {
26✔
220
                pts.Fprintf("cd %s && ", c.Path)
4✔
221
        }
4✔
222
        if len(c.Envs) > 0 {
30✔
223
                pts.Fprintf("%s ", strings.Join(c.Envs, " "))
8✔
224
        }
8✔
225
        if c.ShellType != "" && c.ShellFlag != "" {
40✔
226
                pts.Fprintf("%s %s '%s'", c.ShellType, c.ShellFlag, escapeSingleQuotes(makeCommandMessage(name, args)))
18✔
227
        } else {
22✔
228
                pts.Fprintf("%s %s", name, strings.Join(args, " "))
4✔
229
        }
4✔
230
        return pts.String()
22✔
231
}
232

233
// StreamExec executes a shell command with the specified name and arguments, using the CommandConfig configuration, and returns the output as a byte slice.
234
// StreamExec 使用 CommandConfig 的配置执行带有指定名称和参数的 shell 命令,并返回输出的字节切片。
235
func (c *CommandConfig) StreamExec(name string, args ...string) ([]byte, error) {
2✔
236
        return c.ExecInPipe(name, args...)
2✔
237
}
2✔
238

239
// ExecInPipe executes a shell command with the specified name and arguments, using the CommandConfig configuration, and returns the output as a byte slice.
240
// ExecInPipe 使用 CommandConfig 的配置执行带有指定名称和参数的 shell 命令,并返回输出的字节切片。
241
func (c *CommandConfig) ExecInPipe(name string, args ...string) ([]byte, error) {
2✔
242
        if err := c.checkConfig(name, args); err != nil {
2✔
243
                return nil, erero.Ero(err)
×
244
        }
×
245
        command := c.prepareCommand(name, args)
2✔
246

2✔
247
        stdoutPipe, err := command.StdoutPipe()
2✔
248
        if err != nil {
2✔
249
                return nil, erero.Wro(err)
×
250
        }
×
251

252
        stderrPipe, err := command.StderrPipe()
2✔
253
        if err != nil {
2✔
254
                return nil, erero.Wro(err)
×
255
        }
×
256

257
        stdoutReader := bufio.NewReader(stdoutPipe)
2✔
258
        stderrReader := bufio.NewReader(stderrPipe)
2✔
259
        if err := command.Start(); err != nil {
2✔
260
                return nil, erero.Wro(err)
×
261
        }
×
262

263
        wg := sync.WaitGroup{}
2✔
264
        wg.Add(2)
2✔
265
        var errMatch = false
2✔
266
        var stderrBuffer = printgo.NewPTX()
2✔
267
        go func() {
4✔
268
                defer wg.Done()
2✔
269
                errMatch = c.readPipe(stderrReader, stderrBuffer, "REASON", eroticgo.RED)
2✔
270
        }()
2✔
271
        var outMatch = false
2✔
272
        var stdoutBuffer = printgo.NewPTX()
2✔
273
        go func() {
4✔
274
                defer wg.Done()
2✔
275
                outMatch = c.readPipe(stdoutReader, stdoutBuffer, "OUTPUT", eroticgo.GREEN)
2✔
276
        }()
2✔
277
        wg.Wait()
2✔
278

2✔
279
        if outMatch {
2✔
280
                return utils.WarpMessage(done.VAE(stdoutBuffer.Bytes(), nil), c.DebugMode)
×
281
        }
×
282

283
        if errMatch { //比如 "go: upgraded github.com/xx/xx vxx => vxx" 这就不算错误,而是正确的
2✔
284
                return utils.WarpMessage(done.VAE(stderrBuffer.Bytes(), nil), c.DebugMode)
×
285
        }
×
286

287
        if stderrBuffer.Len() > 0 {
2✔
288
                return utils.WarpMessage(done.VAE(stdoutBuffer.Bytes(), erero.New(stderrBuffer.String())), c.DebugMode)
×
289
        } else {
2✔
290
                return utils.WarpMessage(done.VAE(stdoutBuffer.Bytes(), nil), c.DebugMode)
2✔
291
        }
2✔
292
}
293

294
// readPipe reads from the provided reader and writes to the provided PTX buffer, using the specified debug message and colors.
295
// readPipe 从提供的 reader 读取数据并写入提供的 PTX 缓冲区,使用指定的调试消息和颜色。
296
func (c *CommandConfig) readPipe(reader *bufio.Reader, ptx *printgo.PTX, debugMessage string, erotic eroticgo.COLOR) (matched bool) {
4✔
297
        for {
10✔
298
                streamLine, _, err := reader.ReadLine()
6✔
299

6✔
300
                if c.DebugMode {
12✔
301
                        zaplog.SUG.Debugln(debugMessage, erotic.Sprint(string(streamLine)))
6✔
302
                }
6✔
303

304
                if (c.MatchMore || !matched) && c.MatchPipe != nil {
6✔
305
                        if c.MatchPipe(string(streamLine)) {
×
306
                                matched = true
×
307
                        }
×
308
                }
309

310
                if err != nil {
10✔
311
                        if err == io.EOF {
8✔
312
                                ptx.Write(streamLine)
4✔
313
                                return matched
4✔
314
                        }
4✔
315
                        panic(erero.Wro(err)) //panic: 读取结果出错很罕见
×
316
                } else {
2✔
317
                        ptx.Write(streamLine)
2✔
318
                        ptx.Println()
2✔
319
                }
2✔
320
        }
321
}
322

323
// ShallowClone creates a shallow copy of the CommandConfig instance.
324
// ShallowClone 拷贝个新的 CommandConfig 实例,以便于实现总配置和子配置分隔.
325
func (c *CommandConfig) ShallowClone() *CommandConfig {
×
NEW
326
        return &CommandConfig{
×
NEW
327
                Envs:      slices.Clone(c.Envs), //这里为了避免踩内存还是得拷贝一份
×
NEW
328
                Path:      c.Path,               //在相同的位置
×
NEW
329
                ShellType: "",                   //各个命令会自己设置
×
NEW
330
                ShellFlag: "",                   //各个命令会自己设置
×
NEW
331
                DebugMode: c.DebugMode,          //使用相同的
×
NEW
332
                MatchPipe: nil,                  //各个命令会自己设置
×
NEW
333
                MatchMore: false,                //各个命令会自己设置
×
NEW
334
                TakeExits: make(map[int]string), //这里很简单因为不同的子命令期望的错误码不同,这里就不克隆这个“有预期的错误码表”,避免错误被忽略
×
NEW
335
        }
×
UNCOV
336
}
×
337

338
// GetSubClone creates a shallow copy of the CommandConfig instance with a new path and returns the updated instance.
339
// GetSubClone 创建一个带有新路径的 CommandConfig 实例的浅拷贝并返回更新后的实例。
340
func (c *CommandConfig) GetSubClone(path string) *CommandConfig {
×
341
        return c.ShallowClone().WithPath(path)
×
342
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc