Canalize a saída de um comando se for bem-sucedido

14
INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml | head -1 | xargs basename`

Eu queria executar o segundo comando ( head -1) apenas se o primeiro comando for bem-sucedido. Como melhoro este comando?

Govind Kailas
fonte
O que você quer dizer com sucesso? lsnão falhará.
Matteo
2
Mas a glob pode falhar se não houver arquivos correspondentes.
Tripleee

Respostas:

8

Tente o seguinte:

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml | head -1 | xargs -r basename`

Passar xargso -rsinalizador fará com que ele seja executado apenas basenamese ler pelo menos um item da entrada padrão (head -1 ).

head -1 será executado, mas você não verá nem capturará nenhuma saída dele.

Além disso, se você não quiser que o usuário veja nenhuma saída de erro ls, poderá redirecionar lso fluxo stderr para /dev/null.

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename`

Observe também que adicionei aspas $MY_DIR. Dessa forma, o comando não falhará se $MY_DIRcontiver espaços.

Se você estiver usando um shell moderno como o bash, use um $( )shell de captura em vez de backticks. Você também deve mudar o estilo de suas variáveis. Geralmente, você deve evitar o uso de nomes de variáveis ​​com todas as letras maiúsculas nos scripts. Esse estilo geralmente é reservado para variáveis ​​reservadas e ambientais.

input_file=$(ls -rt "$my_dir"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename)

fonte
Isso não funciona se não houver arquivos?
Mel Boyce
Na verdade: ls -rt GLOB 2>/dev/null | head -n1 | xargs -r basenamefunciona.
Mel Boyce
@MelBoyce: Adicionei isso à minha resposta. Obrigado.
ls é uma ferramenta para analisar interativamente as informações do arquivo. Sua saída é formatada para humanos e causará erros nos scripts. Use globs ou encontre. Entenda o porquê: mywiki.wooledge.org/ParsingLs
Cinelli
@cinelli: Isso é verdade. Eu estava apenas respondendo a pergunta.
9

Em uma linha de tubulação, todos os comandos são iniciados e executados simultaneamente, não um após o outro. Então você precisa armazenar a saída em algum lugar.

if ls_output=$(ls -rtd -- "$MY_DIR"/FILE.*.xml); then
  first_file=$(printf '%s\n' "$ls_output" | head -n 1)
  first_file_name=$(basename -- "$first_file")
fi

Observe que ele assume que os nomes dos arquivos não contêm caracteres de nova linha. O uso xargstambém significaria problemas com caracteres em branco, aspas simples e duplas e barras invertidas. Deixar as variáveis ​​sem aspas significaria problemas com caracteres de espaço, tabulação e curinga. Esquecer --significaria problemas com nomes de arquivos começando com -.

Para obter o nome de base do arquivo mais antigo zshsem nenhuma dessas restrições de caracteres (também evita o problema do tamanho limitado dos argumentos de um comando):

 first_file_name=($MY_DIR/FILE.*.xml(Om[1]:t))

Se não houver correspondência, esse comando falhará e abortará o script. Você também pode fazer:

 first_file_name=($MY_DIR/FILE.*.xml(NOm[1]:t))

Nesse caso, o first_file_name matriz conterá 1 elemento, se houver uma correspondência, ou 0, se não houver. Você pode então fazer:

 if (($#first_file_name)); then
    printf 'Match: %s\n' $first_file_name
 else
    echo >&2 No match
 fi
Stéphane Chazelas
fonte
4

Encontre o arquivo modificado mais recente em um diretório:

latest() {
  local file path=${1:-.} ext=${2-} latest
  for file in "${path%/}"/*"$ext"; do 
    [[ $file -nt $latest ]] && latest=$file
  done
  [[ $latest ]] && printf '%s\n' "$latest"
}

Uso: latest [directory/path/ [.extension]]

Em vez de chamar basename, use expansão de parâmetro.

in_file=$(latest in/my/dir .xml)
base_fn=${in_file##*/} base_fn=${base_fn%.*}

Em um diretório com este conteúdo:

foo.xml bar.xml baz.xml newest.xml

O conteúdo da variável base_fn seria: newest

Para usá-lo corretamente para servir ao propósito de sua solicitação:

check_dir=/path/to/check
check_ext=.xml
if in_file=$(latest "$check_dir" "$check_ext"); then
  base_fn=${in_file##*/} base_fn=${base_fn%.*}
else
  printf '%s\n' "No file found in $check_dir" >&2
fi

EDIT: após a revisão da questão, percebi que o lscomando em questão está procurando o arquivo mais antigo em um diretório. essa mesma função pode ser renomeada paraoldest e ter[[ $file -ot $oldest ]] && oldest=$file o mesmo efeito. desculpas por qualquer confusão.

o importante a ser observado é que você absolutamente, sob nenhuma circunstância jamais na humanidade, deve analisar a produção de ls. Nunca.

Josh McGee
fonte
Observe que, ao contrário das lsabordagens baseadas em, se houver links simbólicos, o tempo de modificação do destino dos links simbólicos será considerado (adicione a -Lopção lspara obter o mesmo comportamento).
Stéphane Chazelas
0

Eu acho que a melhor opção aqui é:

ls -rf $MY_DIR/FILE.*.xml
if [ $? -eq 0 ]; then
      INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml|head -1|xargs basename `
else
      echo "Error!"
fi

se não houver arquivo, o código de retorno de ls será 2, mas se encontrar algum arquivo será 0.

BitsOfNix
fonte
2
Em vez de comparar $?contra 0, você pode fazer isso: if ls -rf $MY_DIR/FILE.*.xml ; then.
Além disso, você pode redirecionar a saída do primeiro comando para /dev/null. ls -rf $MY_DIR/FILE.*.xml &> /dev/null. Dessa forma, o usuário não verá a saída extra.