Files
llgo/internal/shellparse/shellparse.go
2025-09-07 16:37:38 +08:00

87 lines
2.2 KiB
Go

/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package shellparse
import (
"fmt"
"strings"
"unicode"
)
// Parse parses a shell command string into command and arguments,
// properly handling quoted arguments with spaces
func Parse(cmd string) ([]string, error) {
args := make([]string, 0)
var current strings.Builder
var inQuotes bool
var quoteChar rune
var hasContent bool // Track if we've seen content (including empty quotes)
runes := []rune(cmd)
for i := 0; i < len(runes); i++ {
r := runes[i]
switch {
case !inQuotes && (r == '"' || r == '\''):
// Start of quoted string
inQuotes = true
quoteChar = r
hasContent = true // Empty quotes still count as content
case inQuotes && r == quoteChar:
// End of quoted string
inQuotes = false
quoteChar = 0
case !inQuotes && unicode.IsSpace(r):
// Space outside quotes - end current argument
if hasContent {
args = append(args, current.String())
current.Reset()
hasContent = false
}
case inQuotes && r == '\\' && i+1 < len(runes):
// Handle escape sequences in quotes
if quoteChar == '"' {
next := runes[i+1]
if next == quoteChar || next == '\\' {
current.WriteRune(next)
i++ // Skip the next rune
} else {
current.WriteRune(r)
}
} else {
// In single quotes, backslash is a literal character.
current.WriteRune(r)
}
default:
// Regular character
current.WriteRune(r)
hasContent = true
}
}
// Handle unterminated quotes
if inQuotes {
return nil, fmt.Errorf("unterminated quote in command: %s", cmd)
}
// Add final argument if any
if hasContent {
args = append(args, current.String())
}
return args, nil
}