Skip to content

Commit 12d1038

Browse files
Fix wildcard sync command if files have special characters
1 parent c280956 commit 12d1038

File tree

2 files changed

+21
-9
lines changed

2 files changed

+21
-9
lines changed

command/context.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strconv"
88
"strings"
99

10+
"github.com/kballard/go-shellquote"
1011
"github.com/peak/s5cmd/v2/storage/url"
1112
"github.com/urfave/cli/v2"
1213
)
@@ -68,7 +69,7 @@ func generateCommand(c *cli.Context, cmd string, defaultFlags map[string]interfa
6869

6970
var args []string
7071
for _, url := range urls {
71-
args = append(args, fmt.Sprintf("%q", url.String()))
72+
args = append(args, shellquote.Join(url.String()))
7273
}
7374

7475
flags := []string{}

command/context_test.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,17 @@ func TestGenerateCommand(t *testing.T) {
3131
mustNewURL(t, "s3://bucket/key1"),
3232
mustNewURL(t, "s3://bucket/key2"),
3333
},
34-
expectedCommand: `cp "s3://bucket/key1" "s3://bucket/key2"`,
34+
expectedCommand: `cp s3://bucket/key1 s3://bucket/key2`,
35+
},
36+
{
37+
name: "empty-cli-flags-with-special-char",
38+
cmd: "cp",
39+
flags: []cli.Flag{},
40+
urls: []*url.URL{
41+
mustNewURL(t, "s3://bucket/key '\"1"),
42+
mustNewURL(t, "s3://bucket/key2"),
43+
},
44+
expectedCommand: `cp 's3://bucket/key '\''"1' s3://bucket/key2`,
3545
},
3646
{
3747
name: "empty-cli-flags-with-default-flags",
@@ -45,7 +55,7 @@ func TestGenerateCommand(t *testing.T) {
4555
mustNewURL(t, "s3://bucket/key1"),
4656
mustNewURL(t, "s3://bucket/key2"),
4757
},
48-
expectedCommand: `cp --acl='public-read' --raw='true' "s3://bucket/key1" "s3://bucket/key2"`,
58+
expectedCommand: `cp --acl='public-read' --raw='true' s3://bucket/key1 s3://bucket/key2`,
4959
},
5060
{
5161
name: "cli-flag-with-whitespaced-flag-value",
@@ -63,7 +73,7 @@ func TestGenerateCommand(t *testing.T) {
6373
mustNewURL(t, "s3://bucket/key1"),
6474
mustNewURL(t, "s3://bucket/key2"),
6575
},
66-
expectedCommand: `cp --cache-control='public, max-age=31536000, immutable' --raw='true' "s3://bucket/key1" "s3://bucket/key2"`,
76+
expectedCommand: `cp --cache-control='public, max-age=31536000, immutable' --raw='true' s3://bucket/key1 s3://bucket/key2`,
6777
},
6878
{
6979
name: "same-flag-should-be-ignored-if-given-from-both-default-and-cli-flags",
@@ -81,7 +91,7 @@ func TestGenerateCommand(t *testing.T) {
8191
mustNewURL(t, "s3://bucket/key1"),
8292
mustNewURL(t, "s3://bucket/key2"),
8393
},
84-
expectedCommand: `cp --raw='true' "s3://bucket/key1" "s3://bucket/key2"`,
94+
expectedCommand: `cp --raw='true' s3://bucket/key1 s3://bucket/key2`,
8595
},
8696
{
8797
name: "ignore-non-shared-flag",
@@ -118,7 +128,7 @@ func TestGenerateCommand(t *testing.T) {
118128
mustNewURL(t, "s3://bucket/key1"),
119129
mustNewURL(t, "s3://bucket/key2"),
120130
},
121-
expectedCommand: `cp --concurrency='6' --flatten='true' --force-glacier-transfer='true' --raw='true' "s3://bucket/key1" "s3://bucket/key2"`,
131+
expectedCommand: `cp --concurrency='6' --flatten='true' --force-glacier-transfer='true' --raw='true' s3://bucket/key1 s3://bucket/key2`,
122132
},
123133
{
124134
name: "string-slice-flag",
@@ -133,7 +143,7 @@ func TestGenerateCommand(t *testing.T) {
133143
mustNewURL(t, "/source/dir"),
134144
mustNewURL(t, "s3://bucket/prefix/"),
135145
},
136-
expectedCommand: `cp --exclude='*.log' --exclude='*.txt' "/source/dir" "s3://bucket/prefix/"`,
146+
expectedCommand: `cp --exclude='*.log' --exclude='*.txt' /source/dir s3://bucket/prefix/`,
137147
},
138148
{
139149
name: "command-with-multiple-args",
@@ -142,10 +152,11 @@ func TestGenerateCommand(t *testing.T) {
142152
urls: []*url.URL{
143153
mustNewURL(t, "s3://bucket/key1"),
144154
mustNewURL(t, "s3://bucket/key2"),
155+
mustNewURL(t, "s3://bucket/key3 .txt"),
145156
mustNewURL(t, "s3://bucket/prefix/key3"),
146157
mustNewURL(t, "s3://bucket/prefix/key4"),
147158
},
148-
expectedCommand: `rm "s3://bucket/key1" "s3://bucket/key2" "s3://bucket/prefix/key3" "s3://bucket/prefix/key4"`,
159+
expectedCommand: `rm s3://bucket/key1 s3://bucket/key2 's3://bucket/key3 .txt' s3://bucket/prefix/key3 s3://bucket/prefix/key4`,
149160
},
150161
{
151162
name: "command-args-with-spaces",
@@ -155,7 +166,7 @@ func TestGenerateCommand(t *testing.T) {
155166
mustNewURL(t, "file with space"),
156167
mustNewURL(t, "wow wow"),
157168
},
158-
expectedCommand: `rm "file with space" "wow wow"`,
169+
expectedCommand: `rm 'file with space' 'wow wow'`,
159170
},
160171
}
161172
for _, tc := range testcases {

0 commit comments

Comments
 (0)