Script Bash que lê nomes de arquivos de um pipe ou de argumentos de linha de comando?

6

Eu quero que meu script leia um monte de nomes de arquivos (que podem ter espaços) dados como glob ou STDIN e faça coisas com eles. Eu fui capaz de ler de qualquer maneira separadamente, mas não combiná-los.

Isto lê globs da linha de comando:

for filename in "$@"; do
    process_file "$filename"
done

E isso lê a partir de STDIN:

IFS=$'\n' read -d '' -r -a filenames
for filename in "${filenames[@]}"; do
    process_file "$filename"
done

O que eu realmente quero é ler qualquer um deles em um array, então não tenho que duplicar todo o meu for filename in... loop duas vezes. Desculpe se isso é óbvio, sou novo no BASH.

EDIT: Eu acho que a melhor coisa seria ler args se eles são dados, e caso contrário, espere STDIN . Como eu faria isso?

EDIT: OK, o problema não é o que eu pensava que era. O problema é que process_file também pede entrada do usuário. Existe uma maneira de ler de STDIN até EOF, armazenar isso e, em seguida, começar a pedir a entrada novamente?

    
por Jeff 14.01.2013 / 01:12

5 respostas

3

Você pode usar xargs para transformar STDIN (mesmo com muitas entradas, muito maiores que o buffer de linha de comando) para argumentos .

Ou então, algo como:

if [ $# -gt 0 ] ;then
    for filename in  "$@" ; do
        process_file "$filename"
    done
  else
    IFS=$'\n' read -d '' -r -a filenames
    for filename in "${filenames[@]}"; do
        process_file "$filename"
    done
  fi

ou ambos:

  IFS=$'\n' read -d '' -r -a filenames
  for filename in "${filenames[@]}" "$@" ; do
        process_file "$filename"
    done
  fi

ou adicionando tal opção , como -l representam apenas argumentos de linha , para garantir que não haja leitura (espera) de STDIN :

case "$1" in
    -l )
        shift
        for filename in "$@" ; do
            process_file "$filename"
        done
        ;;
     * )
        for filename in "${filenames[@]}" "$@" ; do
            process_file "$filename"
        done
        ;;
    esac

(não testado!)

    
por 14.01.2013 / 01:28
5

Ferramentas de processamento de texto tradicionalmente lêem entrada da entrada padrão quando você não especifica nenhum nome de arquivo na linha de comando. Você pode verificar se há argumentos de linha de comando testando a variável $# . Supondo que process_file leia a entrada padrão se você não passar um argumento:

if [ $# -eq 0 ]; then
  process_file
else
  for x; do
    process_file "$x"
  done
fi

Como alternativa, se process_file exigir um argumento, experimente passar /dev/stdin para ler da entrada padrão:

if [ $# -eq 0 ]; then set /dev/stdin; fi
for x; do
  process_file "$x"
done

Você solicitou a leitura de uma lista de nomes de arquivos da entrada padrão quando não há argumentos de linha de comando. Isso é possível, claro, mas eu não aconselho, porque é incomum. Seus usuários terão que aprender uma convenção diferente da maioria das outras ferramentas. No entanto, aqui está uma maneira de fazer isso:

if [ $# -eq 0 ]; then
  set -f # turn off globbing
  IFS='
' # set newlines to be the only field separator
  set -- $(cat)
  set +f; unset IFS
fi
for x; do
  process_file "$x"
done
    
por 14.01.2013 / 01:31
1

Este é um script que eu escrevi para abrir arquivos no gvim do cygwin X11 e no windows gvim com segurança. Ele é projetado como um invólucro de shell e essa é a parte que faz o que você quer:

if [ ${#} -ne 0 ]; then #if we have filenames as CLArgs...
    [[ ! -z ${DEBUG} ]] && echo "Got arguments [${#}]:'${@}'"
    for OFILE in "${@}"; do
        [[ -h ${OFILE} ]] && OFILE="$(readlink -f "${OFILE}")"
        [[ ${WINVIM} == true ]] && OFILE=$(cygpath -wal "${OFILE}")
        echo "\"${VIMRUN}\" --servername GVIM $RT \"${OFILE}\""
        "${VIMRUN}" --servername GVIM $RT "${OFILE}" &
        RT="--remote-tab"
    done
else #otherwise read from stdin safely and handle spaces...
    while read OFILE; do
        [[ -h ${OFILE} ]] && OFILE="$(readlink -f "${OFILE}")"
        [[ ${WINVIM} == true ]] && OFILE=$(cygpath -wal "${OFILE}")
        echo "\"${VIMRUN}\" --servername GVIM $RT \"${OFILE}\""
        "${VIMRUN}" --servername GVIM $RT "${OFILE}" &
        RT="--remote-tab"
    done
fi
    
por 17.06.2014 / 16:38
1

Isso funciona para mim:

for filename in ${*:-'cat'}; do
    process_file "$filename"
done
${*:-'cat'}

expandirá primeiro os argumentos da linha de comando. Se isso não for definido (substituição do parâmetro bash: -), ele usará stdin do pipe.

    
por 21.12.2015 / 10:12
1

Com bash4.4 e acima:

if [ "$#" -eq 0 ]; then
  readarray -d '' args
  set -- "${args[@]}"
fi

for arg do
   something with "$arg"
done
    
por 21.12.2015 / 11:12