postando dados usando cURL em um script

3

Estou tentando escrever um script alternativo simples para o upload de arquivos para o serviço transfer.sh . Um dos exemplos no site menciona uma maneira de fazer upload de vários arquivos em uma única "sessão":

$ curl -i -F filedata=@/tmp/hello.txt \
  -F filedata=@/tmp/hello2.txt https://transfer.sh/

Eu estou tentando fazer uma função que levaria qualquer número de argumentos (arquivos) e passá-los para cURL dessa maneira. A função é a seguinte:

transfer() {
    build() {
        for file in $@
        do
            printf "-F filedata=@%q " $file
        done
    }

    curl --progress-bar -i \
        $(build $@) https://transfer.sh | grep https
}

A função funciona conforme o esperado, desde que não haja espaços nos nomes dos arquivos. A saída de printf "-f filedata=@%q " "hello 1.txt" é -F filedata=@test\ 1.txt , então esperei que os caracteres especiais fossem escapados corretamente. No entanto, quando a função é chamada com um nome de arquivo que inclui espaços:

$ transfer hello\ 1.txt

cURL não parece interpretar as fugas e relata um erro:

curl: (26) couldn't open file "test\"

Eu também tentei citar partes do comando, por exemplo printf "-F filedata=@\"%s\" " "test 1.txt" , que produz -F filedata=@"test 1.txt" . Nesse caso, cURL retorna o mesmo erro: curl: (26) couldn't open file ""test" . Parece que não se importa com as citações.

Não tenho certeza do que causa esse comportamento. Como eu poderia corrigir a função para também trabalhar com nomes de arquivos que incluem espaço em branco?

Editar / Solução

Foi possível resolver o problema usando uma matriz, conforme explicado por @meuh. Uma solução que funciona em bash e zsh é:

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
}

A saída em zsh e bash é:

$ 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

Pode ser uma boa ideia enviar a saída da função para a área de transferência com xsel --clipboard ou xclip no Linux e pbcopy no OS X.

A solução fornecida pelo @Jay também funciona perfeitamente:

transfer() {
  printf -- "-F filedata=@%s
$ curl -i -F filedata=@/tmp/hello.txt \
  -F filedata=@/tmp/hello2.txt https://transfer.sh/
" "$@" \ | xargs -0 sh -c \ 'curl --progress-bar -i "$@" https://transfer.sh | grep -i https' zerop }
    
por Bruno 09.05.2016 / 18:51

2 respostas

2

Uma forma de evitar a divisão de palavras é usar uma matriz para carregar cada argumento sem precisar escapar:

push(){ args[${#args[*]}]="$1"; }
build() {
    args=()
    for file
    do  push "-F"
        push "filedata=@$file"
    done
}
build "$@"
curl --progress-bar -i "${args[@]}" https://transfer.sh | grep https

A função build cria uma matriz args e a função push adiciona um novo valor ao final da matriz. O curl simplesmente usa o array.

A primeira parte pode ser simplificada, pois push também pode ser escrito como args+=("$1") , para que possamos removê-la e alterar build para

build() {
    args=()
    for file
    do  args+=("-F" "filedata=@$file")
    done
}
    
por 09.05.2016 / 19:49
3

Experimente esta solução padrão e segura:

transfer() {
  printf -- "-F filedata=@%s
$ ls -b
f\ rst  s'cond

$ transfer() { printf -- "-F filedata=@%s
transfer() {
  printf -- "-F filedata=@%s
$ ls -b
f\ rst  s'cond

$ transfer() { printf -- "-F filedata=@%s%pre%" "$@" | xargs -0 sh -c 'curl --progress-bar -i "$@" https://transfer.sh | grep -i https' zerop ;}

$ transfer *
######################################################################## 100.0%
https://transfer.sh/fnzuh/f-rst
https://transfer.sh/fnzuh/scond
" "$@" | xargs -0 sh -c 'curl --progress-bar -i "$@" https://transfer.sh | grep -i https' zerop }
" "$@" | xargs -0 sh -c 'curl --progress-bar -i "$@" https://transfer.sh | grep -i https' zerop ;} $ transfer * ######################################################################## 100.0% https://transfer.sh/fnzuh/f-rst https://transfer.sh/fnzuh/scond
" "$@" | xargs -0 sh -c 'curl --progress-bar -i "$@" https://transfer.sh | grep -i https' zerop }

"$@" com as Double-Quotes fará com que o shell mantenha as opções originais inalteradas. Veja a seção 2.5.2 Parâmetros Especiais de Especificações do Open Group Base Edição 6 :

@

Expands to the positional parameters, starting from one. When the expansion occurs within double-quotes, and where field splitting (see Field Splitting) is performed, each positional parameter shall expand as a separate field, with the provision that the expansion of the first parameter shall still be joined with the beginning part of the original word (assuming that the expanded parameter was embedded within a word), and the expansion of the last parameter shall still be joined with the last part of the original word. If there are no positional parameters, the expansion of '@' shall generate zero fields, even when '@' is double-quoted.

Usar printf dessa maneira é uma maneira padrão de lidar com opções. Você pode testá-lo e ver que o resultado será gerar quantas -F filedata=@ strings como o número de opções.

O teste com 2 arquivos com um caracter especial em seus nomes:

%pre%     
por 09.05.2016 / 19:23