Программирование оболочки, избегая временных файлов

8

Я часто пишу сценарии оболочки KSH, которые следуют той же схеме:

  • (1) получить вывод из одной или нескольких команд
  • (2) отформатируйте его, используя grep | cut | awk | sed, и распечатайте его на экране или в файле

Для этого я часто сохраняю вывод (1) в временном файле, а затем выполняю форматирование в (2) для этого файла.

Возьмите этот код, например:

TMPFILE=file.tmp

# If tmpfile exists rm it.
[ -f $TMPFILE ] && rm -f $TMPFILE

for SERVICE in $(myfunc); do
    getInfo $SERVICE > $TMPFILE # Store raw output in the TMPFILE

    # I retrieve the relevant data from the TMPFILE
    SERV_NAME=$(head -1 $TMPFILE | sed -e 's/ $//')
    SERV_HOSTNAME=$(grep HOSTNAME $TMPFILE | cut -d "=" -f2)
    SERV_ARGS=$(grep Arguments $TMPFILE | cut -d ":" -f2)

    print $SERV_NAME $SEP $SERV_HOSTNAME $SEP $SERV_ARGS
    rm -f $TMPFILE #rm the TMPFILE in vue of next iteration
done

Есть ли способ, используя каналы, перенаправления и т. Д., Чтобы избежать записи файла на диск каждый раз?

Если это поможет, я использую ksh Версия M-11/16 / 88i

rahmu
источник
3
Хорошо бы избегать имен переменных ALL_CAPS в сценариях оболочки и обрабатывать это пространство имен как зарезервированное оболочкой, чтобы избежать путаницы в таких важных вещах, как PATHили другие переменные оболочки или среды. TMPFILEможет быть хорошо, но TMPDIRэто особенное, так что вы действительно хотите идти по канату?
jw013
Для потомков: еще один вопрос, который был помечен как дубликат этого unix.stackexchange.com/questions/63923/…, включает ответ с именованными каналами fifo, который также может быть использован здесь (хотя, вероятно, это не лучший вариант в это частный случай).
Златовласка
@goldilocks: Может быть, мы сможем объединить два вопроса в один. Можем ли мы связаться с модератором, чтобы сделать это?
Рахму
@rahmu: я пометил другой вопрос. Я думаю, что это зависит от сил, которые будут сейчас ...
Златовласка

Ответы:

9

Ваш код выглядит как вполне оправданный пример использования мне временных файлов. Я бы остался: придерживайтесь этого подхода. Единственное, что действительно нужно изменить, - это способ создания временного файла. Используйте что-то вроде

 TMP=$(tempfile)

или

 TMP=$(mktemp)

или по крайней мере

 TMP=/tmp/myscript_$$

Таким образом, вы не сможете легко предсказать имя (безопасность) и исключите вмешательство правил между несколькими экземплярами скрипта, работающими одновременно.

rozcietrzewiacz
источник
2
педантично, кавычки не требуются для назначения переменных.
Гленн Джекман
1
@glenn Правда, в этом случае они не должны иметь значения, поскольку каждая из команд обычно создает строку без пробелов. Но это хорошая привычка иметь кавычки в тех случаях, когда вы назначаете вывод команды переменной - так что я буду настаивать на том, чтобы оставить это так.
rozcietrzewiacz
Убраны цитаты в последнем примере для различия.
rozcietrzewiacz
3
@roz Нет, ты упустил суть. Присвоение переменных в оболочке распознается до выполнения каких-либо расширений, и для присваивания переменных НЕ выполняется разделение полей . Таким образом, var=$(echo lots of spaces); echo "$var"это нормально и должен производить lots of spacesкак выходной. Реальное предостережение, о котором никто не упомянул, - замена команд удаляет все завершающие строки. Это не проблема здесь, а имеет значение, например, если у вас есть сломанный, mktempкоторый создал имена файлов с конечными символами новой строки. Обычная работа вокруг, если требуется, есть var=$(echo command with trailing newline; echo x); var=${var%x}.
jw013
1
@ jw013 Да, я понимаю это сейчас - нет, когда я написал ответ год назад. Спасибо за указание на это! (исправление ...)
rozcietrzewiacz
5

Вы можете использовать переменную:

info="$(getInfo $SERVICE)"
SERV_NAME="$(head -1 $TMPFILE <<<"$info" | sed -e 's/ $//')"
...

От man ksh:

<<<word       A  short  form of here document in which word becomes the
              contents of the here-document after any parameter  expan-
              sion,  command  substitution, and arithmetic substitution
              occur.

Преимущества включают в себя:

  • Включает параллельное выполнение.
  • По моему опыту, это намного быстрее, чем временные файлы. Если у вас не так много данных, что вы в конечном итоге меняете их местами, они должны быть на несколько порядков быстрее (только за исключением буферов кеширования HD, которые могут быть такими же быстрыми при небольших объемах данных).
  • Другие процессы или пользователи не могут испортить ваши данные.
l0b0
источник
<<< не существует в моем ksh. Я получаю ошибку, и я не могу найти ее на странице справки. Я использую ksh88. Вы уверены, что эта версия должна иметь эту функцию?
раму
Нет; Я думаю, что я не проверил правильную manстраницу (там не было упоминания номера версии на веб-странице: /)
l0b0
<<<это баш «здесь строка». Я не думаю, что это появляется в любой другой оболочке. (О, zshможет быть ...)
rozcietrzewiacz
2
@rozcietrzewiacz: Google для man ksh. Это было, конечно, упомянуто там.
10
3
Угадайте, как bash реализует здесь-строки и здесь-документы. sleep 3 <<<"here string" & lsof -p $! | grep 0rsleep 30251 anthony 0r REG 253,0 12 263271 /tmp/sh-thd-7256597168 (deleted)- да, он использует временный файл.
Дероберт
2

У вас есть два варианта:

  1. Вы получаете данные один раз (в вашем примере с getInfo) и сохраняете их в файле, как и вы.

  2. Вы извлекаете данные каждый раз и не сохраняете их локально, то есть звоните getInfoкаждый раз

Я не вижу проблемы в создании временного файла, чтобы избежать повторной обработки / повторной выборки.

Если вы беспокоитесь о том, чтобы оставить временный файл где-нибудь, вы всегда можете использовать его, trapчтобы быть уверенным, что удалите его в случае, если сценарий убит / прерван

trap "rm -f $TMPFILE" EXIT HUP INT QUIT TERM

и использовать mktempдля создания уникального имени файла для вашего временного файла.

Matteo
источник
1

Вместо создания файла создайте операторы присваивания оболочки и оцените этот вывод.

for SERVICE in $(myfunc); do
    eval $(getInfo $SERVICE |
               sed -n -e '1/\(.*\) *$/SERV_NAME="\1"/p' \
                   -e '/HOSTNAME/s/^[^=]*=\([^=]*\).*/SERV_HOSTNAME="\1"/p' \
                   -e '/Arguments/^[^:]*:\([^:]*\).*/SERV_ARGS="\1"/p')
    print $SERV_NAME $SEP $SERV_HOSTNAME $SED $SERV_ARGS
done

Или, если вы просто хотите распечатать информацию:

for SERVICE in $(myfunc); do
    getInfo $SERVICE | awk -vsep="$SEP" '
        BEGIN{OFS=sep}
        NR == 1 { sub(/ *$/,""); SERV_NAME=$0 }
        /HOSTNAME/ { split($0, HOST, /=/; SERV_HOSTNAME=HOST[2]; }
        /Arguments/ { split($0, ARGS, /:/; SERV_ARGS }
        END { print SERV_NAME, SERV_HOSTNAME, SERV_ARGS }'
done
Arcege
источник