Странное поведение bash script

Вот фрагмент:

var=`ls | shuf | head -2 | xargs cat | sed -e 's/\(.\)/\1\n/g' | shuf | tr -d '\n'`

Это выберет два случайных файла из текущего каталога, объединит их содержимое, перетасует их и присвоит результат var. Это работает отлично в большинстве случаев, но примерно раз в тысячах случаев, вместо этого только вывод ls привязан к var (это не только выход, см. EDIT II). Что может быть объяснением?

Некоторые потенциально важные факты:

  • каталог содержит не менее двух файлов
  • есть только текстовые файлы в каталоге
  • имена файлов не содержат пробелов
  • файлы длиной от 5 до 1000 символов
  • фрагмент является частью более крупного script, который параллельно запускал два экземпляра
  • bash версия: GNU bash, version 4.1.5(1)-release (i686-pc-linux-gnu)
  • uname: Linux 2.6.35-28-generic-pae #50-Ubuntu

РЕДАКТИРОВАТЬ: Я запускал фрагмент сам по себе несколько тысяч раз без ошибок. Затем я попытался запустить его с помощью других частей всего script. Здесь конфигурация, которая создает ошибки:

cd dir_with_text_files
var=`ls | shuf | head -2 | xargs cat | sed -e 's/\(.\)/\1\n/g' | shuf | tr -d '\n'`
cd ..

Существует несколько сотен строк script между cd s, но это минимальная конфигурация для воспроизведения ошибки. Обратите внимание, что аномальный вывод привязывает к var выход текущего каталога, а не dir_with_text_files.

EDIT II: Я смотрел результаты более подробно. Вывод ls не отображается отдельно, он вместе с двумя перетасованными файлами (между их содержимым или после или перед ними неповрежденными). Но все становится лучше; позвольте мне создать сцену, чтобы поговорить о конкретных каталогах.

[~/projects/upload] ls -1
checked // dir
lines   // dir, the files to shuffle are here
pages   // also dir
proxycheck
singlepost
uploader
indexrefresh
t
tester

До сих пор я видел вывод ls от upload, но теперь я увидел результат ls */* (также пробежал от upload). Он был в форме "someMangledText ls moreMangledText ls */* finalBatchOfText". Возможно ли, что последовательность ls, которая, несомненно, была сгенерирована, каким-то образом выполнена?

Ответы

Ответ 1

Здесь нет проблем. Я также переписал бы сказанное выше:

sed 's:\(.\):\1\n:g' < <(shuf -e * | head -2 | xargs cat) | shuf | tr -d '\n'

Не используйте ls для отображения содержимого каталога, используйте *.
Кроме того, выполните некоторую отладку. Используйте shebang, а затем:

set -e
set -o pipefail

и запустите script следующим образом:

/bin/bash -x /path/to/script

и проверить вывод.
Вместо отладки всего script вы можете окружать только ту часть, которая кажется проблематичной с помощью -x

set -x
...code that may have problems...
set +x

чтобы выход фокусировался на этой части кода. Кроме того, используйте параметр pipefail.

Некоторые определения:

  • -e: немедленно выйдите, если простая команда завершает работу с ненулевым статусом, если только команда, которая терпит неудачу, является частью списка команд сразу после некоторого времени или пока ключевое слово, часть теста в выражении if, не будет && или || list, или если статус возврата команды перевернут с помощью!. Ловушка в ERR, если установлена, выполняется до того, как оболочка выходит из
  • -x: печатать трассировку простых команд, команд, команд случая, команд выбора и арифметики для команд и их аргументов или связанных списков слов после их расширения и перед их выполнением. Значение переменной PS4 расширяется, и итоговое значение печатается перед командой и ее расширенными аргументами
  • pipefail: если установлено, возвращаемое значение конвейера - это значение последней (самой правой) команды для выхода с ненулевым статусом или ноль, если все команды в конвейере успешно завершены.

Ответ 2

В целях отладки вы также можете очистить среду с помощью env -i и отфильтровать непечатаемые символы:

#!/usr/bin/env -i /bin/bash --

set -ef
set -o pipefail

unset IFS PATH LC_ALL
IFS=$' \t\n'
PATH="$(PATH=/bin:/usr/bin getconf PATH)"
LC_ALL=C
export IFS PATH LC_ALL

#var="$((find . -type f -maxdepth 1 -print0 | shuf -z -n 2 | xargs -0 cat) | sed -e 's/\(.\)/\1\n/g' | shuf | tr -d '\n')"

var="$((find . -type f -maxdepth 1 -print0 | shuf -z -n 2 | xargs -0 cat) | tr -cd '[[:print:]]' | grep -o '.' | shuf | tr -d '\n')"

Перед запуском script вы также можете отключить библиотеку readline GNU и! расширение истории стилей:

bash --noediting
set +H

Ответ 3

Основываясь на том, что вы говорите по вашим частотам отказов, и учитывая успех других тестов, выполненных плакатами выше, это звучит как проблема, которая может быть вызвана случайным сбоем смены каталога. Является ли каталог, к которому вы обращаетесь в этом script, установленном с удаленной машины случайно? Если это так, это может быть небольшой и временный сетевой сбой, который не обрабатывается должным образом. (Просто догадаться.)