├── go.mod ├── examples ├── windows │ ├── readme.md │ ├── run_dir_return_output.go │ └── run_dir_print_output_and_cmd.go ├── run_ls.go ├── run_ps_print_output.go ├── run_date_return_output.go ├── run_ls_print_output_with_dir.go ├── run_ps_write_output_to_file.go ├── handle_error.go ├── run_ls_custom_config.go ├── run_kubeclt_get_nodes_with_env.go ├── run_pwd_and_ls.go └── readme.md ├── Makefile ├── .gitignore ├── .github └── workflows │ └── go.yml ├── cmd_test.go ├── cmd_error.go ├── LICENSE ├── config.go ├── README.md └── cmd.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kgs19/cmdx 2 | 3 | go 1.21.0 4 | -------------------------------------------------------------------------------- /examples/windows/readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This directory contains windows specific examples. 3 | -------------------------------------------------------------------------------- /examples/run_ls.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | if err := cmdx.RunCommandPrintOutput("ls", "-l"); err != nil { 10 | log.Fatalf("Command failed: %v", err) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/run_ps_print_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | command := "ps" 10 | args := []string{"aux"} 11 | err := cmdx.RunCommandPrintOutput(command, args...) 12 | if err != nil { 13 | log.Fatalf("Error executing 'ps aux' command: %v", err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/run_date_return_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | command := "date" 10 | args := []string{"+%H:%M"} 11 | out, err := cmdx.RunCommandReturnOutput(command, args...) 12 | if err != nil { 13 | log.Fatalf("Error executing 'date' command: %v", err) 14 | } 15 | println("cmd output: " + out) 16 | } 17 | -------------------------------------------------------------------------------- /examples/run_ls_print_output_with_dir.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | cmdDir := "/tmp" 10 | command := "ls" 11 | args := []string{"-la"} 12 | 13 | err := cmdx.RunCommandPrintOutputWithDirAndEnv(command, cmdDir, nil, args...) 14 | if err != nil { 15 | log.Fatalf("Error executing 'ls -la' command: %v", err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/run_ps_write_output_to_file.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | command := "ps" 10 | args := []string{"aux"} 11 | filePath := "ps_output.txt" 12 | 13 | err := cmdx.RunCommandWriteOutputToFile(command, filePath, args...) 14 | if err != nil { 15 | log.Fatalf("Error executing 'ps aux' command: %v", err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/handle_error.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kgs19/cmdx" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | err := cmdx.RunCommandPrintOutput("somecommand", "--flag") 11 | if cmdErr, ok := err.(*cmdx.CommandError); ok { 12 | fmt.Printf("Command failed with exit code %d\n", cmdErr.ExitCode) 13 | fmt.Printf("Error message: %s\n", cmdErr.ErrorMsg) 14 | } else if err != nil { 15 | log.Fatalf("Unexpected error: %v", err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Add go fmt to format the code 2 | fmt: 3 | @echo "Formatting code..." 4 | go fmt 5 | @echo "Done!" 6 | 7 | run-test: 8 | @echo "Running tests..." 9 | go test -v 10 | @echo "Done!" 11 | 12 | run-examples: 13 | @echo "Running examples..." 14 | cd examples && \ 15 | go run run_ps_print_output.go && \ 16 | go run run_date_return_output.go && \ 17 | go run run_ls_print_output_with_dir.go && \ 18 | go run run_ls_custom_config.go 19 | @echo "Done!" 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/run_ls_custom_config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | command := "ls" 10 | args := []string{"-la"} 11 | 12 | config := cmdx.Config{ 13 | PrintCommandEnabled: true, 14 | CommandDir: "/tmp", 15 | } 16 | cmdx.SetConfig(config) 17 | 18 | err := cmdx.RunCommandPrintOutput(command, args...) 19 | if err != nil { 20 | log.Fatalf("Error executing 'ls -la' command: %v", err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/windows/run_dir_return_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | cmdDir := "c:\\Windows\\System32" 10 | command := "cmd" 11 | args := []string{"/C", "dir"} // bare format (no heading, summary, etc.) 12 | 13 | config := cmdx.Config{ 14 | PrintCommandEnabled: true, 15 | } 16 | cmdx.SetConfig(config) 17 | 18 | out, err := cmdx.RunCommandReturnOutputWithDirAndEnv(command, cmdDir, nil, args...) 19 | if err != nil { 20 | log.Fatalf("Error executing 'dir' command: %v", err) 21 | } 22 | println(out) 23 | } 24 | -------------------------------------------------------------------------------- /examples/run_kubeclt_get_nodes_with_env.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | cmdDir := cmdx.DefaultConfig.CommandDir 10 | kubeconfigFilePath := "/tmp/kubeconfig" 11 | envVars := []string{ 12 | "AWS_PROFILE=dev", 13 | "KUBECONFIG=" + kubeconfigFilePath, 14 | } 15 | 16 | command := "kubectl" 17 | args := []string{"get", "nodes"} 18 | 19 | err := cmdx.RunCommandPrintOutputWithDirAndEnv(command, cmdDir, envVars, args...) 20 | if err != nil { 21 | log.Fatalf("Error executing 'kubectl get nodes' command: %v", err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/windows/run_dir_print_output_and_cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | // example usage of the kcmd package 9 | func main() { 10 | cmdDir := "c:\\Windows\\System32" 11 | command := "cmd" 12 | args := []string{"/C", "dir"} // bare format (no heading, summary, etc.) 13 | 14 | config := cmdx.Config{ 15 | PrintCommandEnabled: true, 16 | } 17 | cmdx.SetConfig(config) 18 | 19 | err := cmdx.RunCommandPrintOutputWithDirAndEnv(command, cmdDir, nil, args...) 20 | if err != nil { 21 | log.Fatalf("Error executing 'dir' command: %v", err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | /.idea/ 27 | /build/ 28 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: 9 | - '*' # matches every branch that doesn't contain a '/' character 10 | pull_request: 11 | branches: 12 | - '*' 13 | jobs: 14 | 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: '1.23' 24 | 25 | - name: run-test 26 | run: | 27 | make run-test 28 | - name: run-examples 29 | run: | 30 | make run-examples -------------------------------------------------------------------------------- /cmd_test.go: -------------------------------------------------------------------------------- 1 | package cmdx 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRunCommandReturnOutputWithDirAndEnv(t *testing.T) { 8 | // Define test cases 9 | tests := []struct { 10 | command string 11 | cmdDir string 12 | envVars []string 13 | args []string 14 | wantErr bool 15 | wantOut string 16 | }{ 17 | {"echo", "", nil, []string{"test"}, false, "test\n"}, 18 | {"invalid_command", "", nil, nil, true, ""}, 19 | } 20 | 21 | for _, tt := range tests { 22 | t.Run(tt.command, func(t *testing.T) { 23 | output, err := RunCommandReturnOutputWithDirAndEnv(tt.command, tt.cmdDir, tt.envVars, tt.args...) 24 | if (err != nil) != tt.wantErr { 25 | t.Errorf("RunCommandReturnOutput() error = %v, wantErr %v", err, tt.wantErr) 26 | } 27 | if output != tt.wantOut { 28 | t.Errorf("RunCommandReturnOutput() output = %v, wantOut %v", output, tt.wantOut) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cmd_error.go: -------------------------------------------------------------------------------- 1 | package cmdx 2 | 3 | import "fmt" 4 | 5 | // CommandError represents an error that occurred while executing a command. 6 | // It includes the exit code, standard error message, and the command that was executed. 7 | type CommandError struct { 8 | ExitCode int 9 | ErrorMsg string 10 | Command string 11 | CmdDir string 12 | } 13 | 14 | func (e *CommandError) Error() string { 15 | if e.CmdDir == "" { 16 | return fmt.Sprintf( 17 | "failed Command: \n%s\n"+ 18 | "exit code: %d\n"+ 19 | "error message: \n%s\n", 20 | e.Command, e.ExitCode, e.ErrorMsg) 21 | } 22 | return fmt.Sprintf( 23 | "failed Command: \n%s\n"+ 24 | "exit code: %d\n"+ 25 | "error message: \n%s\n"+ 26 | "execution directory: %s\n", 27 | e.Command, e.ExitCode, e.ErrorMsg, e.CmdDir) 28 | } 29 | 30 | func NewCommandError(errorMsg string, exitCode int, cmdDir string, command string, args ...string) *CommandError { 31 | return &CommandError{ 32 | ExitCode: exitCode, 33 | ErrorMsg: errorMsg, 34 | CmdDir: cmdDir, 35 | Command: commandWithArgs(command, args...), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 kgs19 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/run_pwd_and_ls.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kgs19/cmdx" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | 10 | // Control the directory where the command will be executed 11 | // There are two ways to set the directory where the command will be executed 12 | // 1. way 13 | // Use the CMDX_COMMAND_DIR environment variable 14 | //This example is to demonstrate how to use the CMDX_COMMAND_DIR environment variable to set the directory where the command will be executed 15 | // Examples 16 | //export CMDX_COMMAND_DIR=/tmp 17 | // or 18 | //export CMDX_COMMAND_DIR=$(pwd) 19 | 20 | // 2. way 21 | //Use the Config struct as shown below 22 | /* 23 | config := cmdx.Config{ 24 | CommandDir: "/tmp", 25 | } 26 | cmdx.SetConfig(config) 27 | */ 28 | print("Command directory: ") 29 | command := "pwd" 30 | err := cmdx.RunCommandPrintOutput(command) 31 | if err != nil { 32 | log.Fatalf("Error executing 'pwd' command: %v", err) 33 | } 34 | println("-") 35 | command = "ls" 36 | args := []string{"-la"} 37 | err = cmdx.RunCommandPrintOutput(command, args...) 38 | if err != nil { 39 | log.Fatalf("Error executing 'ls -la' command: %v", err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package cmdx 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strconv" 7 | ) 8 | 9 | // Config holds the configuration settings for the kcmd library. 10 | type Config struct { 11 | PrintCommandEnabled bool // Flag to enable or disable printing the command executed 12 | CommandDir string // Directory to use to execute the commands 13 | } 14 | 15 | // DefaultConfig provides default settings for the library. 16 | var DefaultConfig = Config{ 17 | PrintCommandEnabled: getEnvAsBool("CMDX_PRINT_COMMAND_ENABLED", false), // Not print the command by default 18 | CommandDir: getCommandDir(), 19 | } 20 | 21 | // SetConfig allows users to set custom configuration options. 22 | func SetConfig(cfg Config) { 23 | DefaultConfig = cfg 24 | } 25 | 26 | // getCommandDir returns the directory for executing the Command. 27 | // It first checks the CMDX_COMMAND_DIR environment variable. 28 | // If not set, it defaults to the directory of the executable. 29 | // 30 | // Returns: 31 | // 32 | // string: The base Command directory. 33 | func getCommandDir() string { 34 | // Check if the KCMD_BASE_COMMAND_DIR environment variable is set 35 | commandDir := getEnv("CMDX_COMMAND_DIR", "") 36 | // If the environment variable is not set, use the directory of the executable 37 | // Also if the baseCommandDir is set to ".", use the directory of the executable 38 | if commandDir == "" { 39 | ex, err := os.Executable() 40 | if err != nil { 41 | panic(err) // Panic if the executable path cannot be determined 42 | } 43 | executablePath := filepath.Dir(ex) 44 | commandDir = executablePath 45 | } 46 | 47 | return commandDir 48 | } 49 | 50 | // getEnv retrieves the value of the environment variable named by the key. 51 | // If the variable is present in the environment, the function returns its value. 52 | // Otherwise, it returns the specified default value. 53 | // 54 | // Parameters: 55 | // - key: The name of the environment variable to look up. 56 | // - defaultVal: The value to return if the environment variable is not set. 57 | // 58 | // Returns: 59 | // 60 | // string: The value of the environment variable or the default value if the variable is not set. 61 | func getEnv(key string, defaultVal string) string { 62 | if value, exists := os.LookupEnv(key); exists { 63 | return value 64 | } 65 | return defaultVal 66 | } 67 | 68 | // Same as getEnv but returns a boolean value. 69 | func getEnvAsBool(name string, defaultVal bool) bool { 70 | valStr := getEnv(name, "") 71 | if val, err := strconv.ParseBool(valStr); err == nil { 72 | return val 73 | } 74 | 75 | return defaultVal 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmdx 2 | A Go library for executing and managing command-line operations with robust error handling and output management. 3 | 4 | ## Overview 5 | The `cmdx` library simplifies command execution in Go applications. 6 | It provides functions to run commands, capture output, and manage errors effectively with structured error types `CommandError`. 7 | Additionally, `cmdx` includes a configurable `Config` struct, which allows users to customize execution settings, 8 | such as enabling command output printing and specifying the default directory for command execution. 9 | 10 | ## Installation 11 | To install the library, run the following command: 12 | ```shell 13 | go get -u github.com/kgs19/cmdx 14 | ``` 15 | 16 | ## Usage 17 | 18 | ### Running Commands 19 | Use `RunCommandPrintOutput` to execute a command and print the output: 20 | ``` 21 | package main 22 | 23 | import ( 24 | "github.com/kgs19/cmdx" 25 | "log" 26 | ) 27 | 28 | func main() { 29 | if err := cmdx.RunCommandPrintOutput("ls", "-l"); err != nil { 30 | log.Fatalf("Command failed: %v", err) 31 | } 32 | } 33 | ``` 34 | 35 | ### Handling Errors with `CommandError` 36 | If a command fails, a `CommandError` instance is returned, providing detailed information: 37 | ``` 38 | package main 39 | 40 | import ( 41 | "errors" 42 | "fmt" 43 | "github.com/kgs19/cmdx" 44 | "log" 45 | ) 46 | 47 | func main() { 48 | err := cmdx.RunCommandPrintOutput("ls", "/non_existent_file.txt") 49 | var cmdErr *cmdx.CommandError 50 | if errors.As(err, &cmdErr) { 51 | fmt.Printf("Command failed with exit code %d\n", cmdErr.ExitCode) 52 | fmt.Printf("Error message: %s\n", cmdErr.ErrorMsg) 53 | fmt.Printf("Execution directory: %s\n", cmdErr.CmdDir) 54 | } else if err != nil { 55 | log.Fatalf("Unexpected error: %v", err) 56 | } 57 | } 58 | ``` 59 | 60 | ### Usage Examples 61 | View the [examples](examples/readme.md) directory for more examples of using the `cmdx` library. 62 | 63 | 64 | ## Configuration 65 | The cmdx library includes configurable settings through the `Config` struct, allowing you to customize command execution: 66 | - `PrintCommandEnabled`: Set to true to enable printing each command before execution. This can be also controlled by the environment variable `CMDX_PRINT_COMMAND_ENABLED`. 67 | - `CommandDir `:Specify the directory for executing the commands. This can be also controlled by the environment variable `CMDX_COMMAND_DIR`. 68 | 69 | Examples of updating configuration: 70 | 1. [example custom config](examples/readme.md#example-2---custom-configuration) 71 | 2. [example env variable](examples/readme.md#example-1---runcommandprintoutput---cmdx_command_dir-env-variable) 72 | 73 | 74 | ## Contributing 75 | Contributions are welcome! Feel free to submit issues or create pull requests. 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | This directory contains example usage of the `cmdx` library. 3 | Below are the details of each example provided. 4 | 5 | ## Basic 6 | ### Example 1 - `RunCommandPrintOutput` 7 | - [Here](./run_ps_print_output.go) is an example to demonstrate how to use the `RunCommandPrintOutput` function to run a command and print the output. 8 | - This example runs the `ps aux` command and prints the output to the console. 9 | 10 | ### Example 2 - `RunCommandReturnOutput` 11 | - [Here](./run_date_return_output.go) is an example to demonstrate how to use the `RunCommandWriteOutputToFile` function to run a command and return the output. 12 | - This example runs the `date +%H:%M` command and save the result to the `out` variable. 13 | - Then it prints the value of the `out` variable to the console. 14 | 15 | ### Example 3 - `RunCommandWriteOutputToFile` 16 | - [Here](./run_ps_write_output_to_file.go) is an example to demonstrate how to use the `RunCommandWriteOutputToFile` function to run a command and write the output to a file. 17 | - This example runs the `ps aux` command and writes the output to the `ps_output.txt` file. 18 | 19 | ### Example 4 - `RunCommandPrintOutputWithDirAndEnv` - `cmdDir` 20 | - [Here](./run_ls_print_output_with_dir.go) is an example to demonstrate how to use the `RunCommandPrintOutputWithDirAndEnv` function to run a command from a specific directory. 21 | - In this example we use set the `cmdDir` argument to run the `ls` command from the `/tmp` directory. 22 | 23 | ### Example 5 - `RunCommandPrintOutputWithDirAndEnv` - `cmdDir` and `envVars` 24 | - [Here](./run_kubeclt_get_nodes_with_env.go) is an example to demonstrate how to use the `RunCommandPrintOutputWithDirAndEnv` function to run a command from a specific directory and set additional environment variables. 25 | - In this example we use the `envVars` argument to: 26 | - programmatically set the `KUBECONFIG` and `AWS_PROFILE` environment variables. 27 | - Then run the `kubectl get nodes` command. 28 | - More information about `cmdDir := cmdx.DefaultConfig.CommandDir` will be provided in the advanced section. 29 | 30 | ## Advanced 31 | 32 | ### Example 1 - `RunCommandPrintOutput` - `CMDX_COMMAND_DIR` env variable 33 | - [Here](./run_pwd_and_ls.go) is an example to demonstrate how to use the `CMDX_COMMAND_DIR` environment variable to control the directory where the command is executed. 34 | - You can set the `CMDX_COMMAND_DIR` environment variable to control the directory where the command is executed. 35 | - Note that the location of the go executable depends on how you run your code, via `go run` or `go build`. 36 | #### Running the example with `go run` 37 | ```shell 38 | cd examples 39 | export CMDX_COMMAND_DIR=$(pwd) 40 | go run run_pwd_and_ls_print_output.go 41 | or 42 | export CMDX_COMMAND_DIR=/tmp 43 | go run run_pwd_and_ls_print_output.go 44 | ``` 45 | - When we use the `go run` a temporary go executable is created in an unpredictable location. 46 | - So it makes sense to set the `CMDX_COMMAND_DIR` to a specific directory. 47 | #### Running the example with `go build` 48 | ```shell 49 | cd examples 50 | go build run_pwd_and_ls_print_output.go 51 | ./run_pwd_and_ls_print_output 52 | ``` 53 | 54 | ### Example 2 - Custom Configuration 55 | - [Here](./run_ls_custom_config.go) is an example to demonstrate how to use the `cmdx.Config` struct to configure the behavior of the library. 56 | - In this example, we set a custom `cmdx.Config` struct to: 57 | 1. Specify the directory where the command is executed. 58 | 2. Enable printing the executed command. 59 | ``` 60 | config := cmdx.Config{ 61 | PrintCommandEnabled: true, 62 | CommandDir: "/tmp", 63 | } 64 | cmdx.SetConfig(config) 65 | ``` -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | package cmdx 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | // RunCommandPrintOutput executes a command and prints the output to os.Stdout. 14 | // 15 | // Parameters: 16 | // - command: The command to be executed 17 | // - args: Additional arguments to pass to the command 18 | // 19 | // Returns: 20 | // - error: An error if the command fails, otherwise nil 21 | func RunCommandPrintOutput(command string, args ...string) error { 22 | cmdDir := DefaultConfig.CommandDir 23 | return RunCommandPrintOutputWithDirAndEnv(command, cmdDir, nil, args...) 24 | } 25 | 26 | // RunCommandPrintOutputWithDirAndEnv runs a command with specified directory and environment variables. 27 | // It prints the output to os.Stdout. 28 | // 29 | // Parameters: 30 | // - command: The command to be executed 31 | // - cmdDir: The directory in which to execute the command 32 | // - envVars: A slice of additional environment variables to set for the command 33 | // - args: Additional arguments to pass to the command 34 | // 35 | // Returns: 36 | // - error: An error if the command fails, otherwise nil 37 | func RunCommandPrintOutputWithDirAndEnv(command string, cmdDir string, envVars []string, args ...string) error { 38 | output := os.Stdout 39 | return runCommand(command, cmdDir, envVars, output, args...) 40 | } 41 | 42 | // RunCommandReturnOutput executes a command and returns the output as string. 43 | // 44 | // Parameters: 45 | // - command: The command to be executed 46 | // - args: Additional arguments to pass to the command 47 | // 48 | // Returns: 49 | // - error: An error if the command fails, otherwise nil 50 | func RunCommandReturnOutput(command string, args ...string) (string, error) { 51 | cmdDir := DefaultConfig.CommandDir 52 | return RunCommandReturnOutputWithDirAndEnv(command, cmdDir, nil, args...) 53 | } 54 | 55 | // RunCommandReturnOutputWithDirAndEnv runs a command with specified directory and environment variables. 56 | // It returns the output as string. 57 | // 58 | // Parameters: 59 | // - command: The command to be executed 60 | // - cmdDir: The directory in which to execute the command 61 | // - envVars: A slice of additional environment variables to set for the command 62 | // - args: Additional arguments to pass to the command 63 | // 64 | // Returns: 65 | // - string: The output of the command as a string 66 | // - error: An error if the command fails, otherwise nil 67 | func RunCommandReturnOutputWithDirAndEnv(command string, cmdDir string, envVars []string, args ...string) (string, error) { 68 | var output bytes.Buffer 69 | err := runCommand(command, cmdDir, envVars, &output, args...) 70 | if err != nil { 71 | return "", err 72 | } 73 | return output.String(), nil 74 | } 75 | 76 | // RunCommandWriteOutputToFile executes a command and writes the output to a specified file. 77 | // 78 | // Parameters: 79 | // - command: The command to be executed 80 | // - args: Additional arguments to pass to the command 81 | // 82 | // Returns: 83 | // - error: An error if the command fails, otherwise nil 84 | func RunCommandWriteOutputToFile(command string, filePath string, args ...string) error { 85 | cmdDir := DefaultConfig.CommandDir 86 | return RunCommandWriteOutputToFileWithDirAndEnv(command, cmdDir, nil, filePath, args...) 87 | } 88 | 89 | // RunCommandWriteOutputToFileWithDirAndEnv runs a command with specified directory and environment variables. 90 | // It writes the output to a specified file. 91 | // 92 | // Parameters: 93 | // - command: The command to be executed 94 | // - cmdDir: The directory in which to execute the command 95 | // - envVars: A slice of additional environment variables to set for the command 96 | // - filePath: The path to the file where the command's output will be written 97 | // - args: Additional arguments to pass to the command 98 | // 99 | // Returns: 100 | // - error: An error if the command fails, otherwise nil 101 | func RunCommandWriteOutputToFileWithDirAndEnv(command string, cmdDir string, envVars []string, filePath string, args ...string) error { 102 | // outFile, err := os.Create(fileAbsPath) 103 | //use append mode to append to the file if it exists 104 | outFile, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) 105 | if err != nil { 106 | return err 107 | } 108 | //close the file when done. 109 | defer func(outFile *os.File) { 110 | err := outFile.Close() 111 | if err != nil { 112 | fmt.Println("Error closing file", err) 113 | } 114 | }(outFile) 115 | 116 | //Set the output to be the file 117 | output := outFile 118 | return runCommand(command, cmdDir, envVars, output, args...) 119 | } 120 | 121 | // runCommand executes a command with specified directory and environment variables. 122 | // It writes the output to the provided writer interface. 123 | // It optionally logs the command being executed and the command directory, see DefaultConfig.PrintCommandEnabled. 124 | // 125 | // Parameters: 126 | // - command: The command to be executed. 127 | // - cmdDir: The directory in which to execute the command 128 | // - envVars: A slice of additional environment variables to set for the command 129 | // - output: An io.Writer where the command's standard output will be written. 130 | // - args: Additional arguments to pass to the command. 131 | // 132 | // Returns: 133 | // - error: An error if the command fails, otherwise nil. 134 | // 135 | // If the command fails, 136 | // it captures the message printed in the standard error and exit code, and returns a custom error. 137 | func runCommand(command string, cmdDir string, envVars []string, output io.Writer, args ...string) error { 138 | exitCode := 0 139 | var errb bytes.Buffer 140 | 141 | if DefaultConfig.PrintCommandEnabled { 142 | // Log the command details 143 | printCmd(command, cmdDir, output, args...) 144 | } 145 | 146 | // Set up the command with the provided directory and arguments 147 | cmd := exec.Command(command, args...) 148 | cmd.Dir = cmdDir 149 | 150 | // Set the environment variables from envVars 151 | setCmdEnvVars(cmd, envVars) 152 | 153 | // pipe the commands output to the applications 154 | // standard output 155 | cmd.Stdout = output 156 | cmd.Stderr = &errb 157 | err := cmd.Run() 158 | 159 | if err != nil { 160 | exitCode = 1 161 | stdErrorMsg := errb.String() 162 | 163 | // If no error message is captured in stderr, use the err.Error() instead 164 | if stdErrorMsg == "" { 165 | stdErrorMsg = err.Error() 166 | } 167 | 168 | var exitError *exec.ExitError 169 | if errors.As(err, &exitError) { // errors.As() -> function allows you to extract a specific error type from the error chain 170 | exitCode = exitError.ExitCode() //try to get actual cmd ExitCode 171 | } 172 | err := NewCommandError(stdErrorMsg, exitCode, cmdDir, command, args...) 173 | return err 174 | } 175 | return nil 176 | } 177 | 178 | func setCmdEnvVars(cmd *exec.Cmd, envVars []string) { 179 | cmd.Env = os.Environ() 180 | if envVars != nil && len(envVars) > 0 { 181 | for _, envVar := range envVars { 182 | cmd.Env = append(cmd.Env, envVar) 183 | } 184 | } 185 | } 186 | 187 | func commandWithArgs(command string, args ...string) string { 188 | return command + " " + strings.Join(args, " ") 189 | } 190 | 191 | func printCmd(command string, cmdDir string, output io.Writer, args ...string) { 192 | // For now do not print envVars may contain sensitive information 193 | cmd := commandWithArgs(command, args...) 194 | if cmdDir != "" { 195 | //Ignore error 196 | _, _ = fmt.Fprintf(output, "Execution directory: %s\n", cmdDir) 197 | } 198 | //print the command to output 199 | //Ignore error 200 | _, _ = fmt.Fprintf(output, "\nExecuting cmd: \n%s\n\n", cmd) 201 | } 202 | --------------------------------------------------------------------------------