I'm trying to write a simple alternative script for uploading files to the transfer.sh service. One of the examples on the website mentions a way of uploading multiple files in a single "session":
$ curl -i -F filedata=@/tmp/hello.txt \
-F filedata=@/tmp/hello2.txt https://transfer.sh/
I'm trying to make a function that would take any number of arguments (files) and pass them to cURL in such fashion. The function is as follows:
transfer() {
build() {
for file in $@
do
printf "-F filedata=@%q " $file
done
}
curl --progress-bar -i \
$(build $@) https://transfer.sh | grep https
}
The function works as expected as long as no spaces are in the filenames.
The output of printf "-f filedata=@%q " "hello 1.txt"
is -F filedata=@test\ 1.txt
, so I expected the special characters to be escaped correctly. However, when the function is called with a filename that includes spaces:
$ transfer hello\ 1.txt
cURL does not seem to interpret the escapes and reports an error:
curl: (26) couldn't open file "test\"
I also tried quoting parts of the command, e.g. printf "-F filedata=@\"%s\" " "test 1.txt"
, which produces -F filedata=@"test 1.txt"
.
In such case cURL returns the same error: curl: (26) couldn't open file ""test"
. It seems that it does not care about quotes at all.
I'm not really sure what causes such behaviour. How could I fix the function to also work with filenames that include whitespace?
Edit/Solution
It was possible to solve the issue by using an array, as explained by @meuh. A solution that works in both bash
and zsh
is:
transfer () {
if [[ "$#" -eq 0 ]]; then
echo "No arguments specified."
return 1
fi
local -a args
args=()
for file in "$@"
do
args+=("-F filedata=@$file")
done
curl --progress-bar -i "${args[@]}" https://transfer.sh | grep https
}
The output in both zsh
and bash
is:
$ ls
test space.txt test'special.txt
$ transfer test\ space.txt test\'special.txt
######################################################################## 100.0%
https://transfer.sh/z5R7y/test-space.txt
https://transfer.sh/z5R7y/testspecial.txt
$ transfer *
######################################################################## 100.0%
https://transfer.sh/4GDQC/test-space.txt
https://transfer.sh/4GDQC/testspecial.txt
It might be a good idea to pipe the output of the function to the clipboard with xsel --clipboard
or xclip
on Linux and pbcopy
on OS X.
The solution provided by @Jay also works perfectly well:
transfer() {
printf -- "-F filedata=@%s\0" "$@" \
| xargs -0 sh -c \
'curl --progress-bar -i "$@" https://transfer.sh | grep -i https' zerop
}
Best Answer
One way to avoid having bash word-splitting is to use an array to carry each argument without any need for escaping:
The
build
function creates an arrayargs
and thepush
function adds a new value to the end of the array. Thecurl
simply uses the array.The first part can be simplified, as
push
can also be written simply asargs+=("$1")
, so we can remove it and changebuild
to