From bb3e03629081360c7d1251f7c4e33aca9a609f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 14 Nov 2023 20:51:52 +0000 Subject: [PATCH] expand: support zero-padding in brace expansions Per "man bash": When either x or y begins with a zero, the shell attempts to force all generated terms to contain the same number of digits, zero-padding where necessary. Fixes #1042. --- expand/braces.go | 25 ++++++++++++++++++++++--- expand/braces_test.go | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/expand/braces.go b/expand/braces.go index 1938a8f0..822ec4d1 100644 --- a/expand/braces.go +++ b/expand/braces.go @@ -5,6 +5,7 @@ package expand import ( "strconv" + "strings" "mvdan.cc/sh/v3/syntax" ) @@ -25,8 +26,17 @@ func Braces(word *syntax.Word) []*syntax.Word { } if br.Sequence { chars := false - from, err1 := strconv.Atoi(br.Elems[0].Lit()) - to, err2 := strconv.Atoi(br.Elems[1].Lit()) + + fromLit := br.Elems[0].Lit() + toLit := br.Elems[1].Lit() + zeros := extraLeadingZeros(fromLit) + // TODO: use max when we can assume Go 1.21 + if z := extraLeadingZeros(toLit); z > zeros { + zeros = z + } + + from, err1 := strconv.Atoi(fromLit) + to, err2 := strconv.Atoi(toLit) if err1 != nil || err2 != nil { chars = true from = int(br.Elems[0].Lit()[0]) @@ -57,7 +67,7 @@ func Braces(word *syntax.Word) []*syntax.Word { if chars { lit.Value = string(rune(n)) } else { - lit.Value = strconv.Itoa(n) + lit.Value = strings.Repeat("0", zeros) + strconv.Itoa(n) } next.Parts = append([]syntax.WordPart{lit}, next.Parts...) exp := Braces(&next) @@ -83,3 +93,12 @@ func Braces(word *syntax.Word) []*syntax.Word { } return []*syntax.Word{{Parts: left}} } + +func extraLeadingZeros(s string) int { + for i, r := range s { + if r != '0' { + return i + } + } + return 0 // "0" has no extra leading zeros +} diff --git a/expand/braces_test.go b/expand/braces_test.go index 2ae4d27d..fe606c80 100644 --- a/expand/braces_test.go +++ b/expand/braces_test.go @@ -133,6 +133,18 @@ var braceTests = []struct { litWord("a{4..1..1}"), litWords("a4", "a3", "a2", "a1"), }, + { + litWord("{1..005}"), + litWords("001", "002", "003", "004", "005"), + }, + { + litWord("{0001..05..2}"), + litWords("0001", "0003", "0005"), + }, + { + litWord("{0..1}"), + litWords("0", "1"), + }, { litWord("a{d..k..3}"), litWords("ad", "ag", "aj"),