Как мне захватить стандартный ввод переменной, не убирая завершающие символы новой строки?

9

В сценарии оболочки ...

Как мне захватить стандартный ввод переменной, не убирая завершающие символы новой строки?

Прямо сейчас я попробовал:

var=`cat`
var=`tee`
var=$(tee)

Во всех случаях $varне будет завершающей новой строки входного потока. Спасибо.

ТАКЖЕ: Если на входе нет завершающего символа новой строки, то решение не должно добавлять его .

ОБНОВЛЕНИЕ В СВЕТЕ ПРИНЯТОГО ОТВЕТА:

Окончательное решение, которое я использовал в своем коде, выглядит следующим образом:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

Если вы хотите увидеть полный код, перейдите на страницу github для расширителя , небольшого сценария оболочки с открытым исходным кодом для фильтра ключевых слов git, который я разработал для целей информационной безопасности. В соответствии с правилами, установленными в файлах .gitattributes (которые могут быть специфичными для отрасли) и git config , git направляет каждый файл через сценарий оболочки expandr.sh при каждом входе или выходе из репозитория. (Вот почему было важно сохранить любые завершающие символы новой строки или их отсутствие.) Это позволяет очищать конфиденциальную информацию и менять местами различные наборы специфических для среды значений для тестовой, промежуточной и активной веток.

CommaToast
источник
то, что вы здесь делаете, не обязательно. filterберет stdin- бежит sed. Вы ловите stdinв $GIT_INPUTраспечатайте его обратно в stdoutтечение трубы к filterи поймать его stdoutв систему, $FILTERED_OUTPUTа затем распечатать его обратно stdout. Все 4 линии в нижней части вашего примера выше можно было бы заменить только это: filter. Здесь не обидно, просто ... ты слишком усердно работаешь. Вам не нужны переменные оболочки большую часть времени - просто направьте ввод в нужное место и передайте его дальше.
mikeserv
Нет, то, что я здесь делаю, необходимо, потому что если я просто сделаю это filter, то он добавит символы новой строки к концам любых входных потоков, которые изначально не заканчивались символами новой строки. На самом деле я изначально только что сделал, filterно столкнулся с той проблемой, которая привела меня к этому решению, потому что ни «всегда добавлять новые строки», ни «всегда удалять новые строки» не являются приемлемыми решениями.
CommaToast
sedвероятно, будет делать дополнительную новую строку - но вы должны справиться с этим filterне со всеми остальными. И все те функции, которые у вас есть, в основном делают одно и то же - а sed s///. Вы используете оболочку для данных труб это сохраненное в памяти , sedтак что sedможет заменить эти данные с другими данными , что оболочка , хранящиеся в его памяти , поэтому sedможно перенаправить его обратно к корпусу. Почему не просто [ "$var" = "$condition" ] && var=new_value? Я также не получаю массивы - вы сохраняете имя массива, а [0]затем используете sedдля замены его значением в [1]? Может быть, чат?
mikeserv
@mikeserv - Какая польза от перемещения этого кода внутри filter? Работает отлично как есть. Относительно того, как работает код по моей ссылке и почему я настроил его так, как я это сделал, да, давайте поговорим об этом в чате.
CommaToast

Ответы:

7

Конечные символы новой строки удаляются перед сохранением значения в переменной. Вы можете сделать что-то вроде:

var=`cat; echo x`

и использовать ${var%x}вместо $var. Например:

printf "%s" "${var%x}"

Обратите внимание, что это решает проблему с завершающими символами новой строки, но не с нулевым байтом (если стандартный ввод не текстовый), так как согласно подстановке команды POSIX :

Если выходные данные содержат нулевые байты, поведение не определено.

Но реализации оболочки могут сохранять нулевые байты.

vinc17
источник
Будут ли в текстовых файлах нулевые байты? Я не понимаю, почему они это сделали. Но сценарий, который вы только что упомянули, кажется, не работает.
CommaToast
@CommaToast Текстовые файлы не содержат нулевых байтов. Но вопрос просто говорит о stdin / input stream, который может не быть текстом в самом общем случае.
vinc17
ХОРОШО. Ну, я попробовал это из командной строки, и он ничего не сделал, и изнутри самого скрипта ваше предложение не сработало, потому что оно добавляет «...» в конце файла. Кроме того, если там не было новой строки, он все равно добавляет ее.
CommaToast
@CommaToast "..." был просто примером. Я уточнил свой ответ. Новая строка не добавляется (см. Текст перед «...» в примере).
vinc17
1
Ну, снаряды не должны скрывать вещи, это не круто. Эти снаряды должны быть выпущены. Мне не нравится, когда мой компьютер думает, что знает лучше меня.
CommaToast
4

Вы можете использовать readвстроенный для выполнения этого:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

Для сценария чтения STDIN это будет просто:

IFS='' read -d '' -r foo

 

Я не уверен, что оболочки это будет работать, хотя. Но отлично работает как в bash, так и в zsh.

Патрик
источник
Ни -dпроцесс подстановки ( <(...)) не являются переносимыми; этот код не будет работать dash, например.
Чепнер
Ну, замена процесса не является частью ответа, это была только часть примера, показывающего, что он работает. Что касается -d, поэтому я положил отказ от ответственности в нижней части. ОП не определяет оболочку.
Патрик
@chepner - хотя стиль немного отличается, концепция, безусловно, работает dash. Вы просто используете <<HEREDOC\n$(gen input)\nHEREDOC\n- в dash- который использует каналы для heredocs так же, как другие оболочки используют их для замены процесса - это не имеет значения. Все read -dдело в том, чтобы просто указать разделитель - вы можете сделать то же самое дюжиной способов - просто будьте в этом уверены. Хотя тебе понадобится хвост gen input.
mikeserv
Вы устанавливаете IFS = '', чтобы он не помещал пробелы между строками, которые он читает, а? Классный трюк.
CommaToast
На самом деле в этом случае, IFS=''вероятно, не нужно. Это означает, что readне будет разрушаться пробелы. Но когда он читает в одну переменную, это не имеет никакого эффекта (что я могу вспомнить). Но я просто чувствую себя безопаснее, оставив его включенным :-)
Патрик
2

Вы можете сделать как:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

Что бы вы ни делали, все равно $varисчезает, как только вы выходите из этой { current shell ; }группировки. Но это также может работать как:

var=$(input | sed '$s/$/./'); var=${var%.}
mikeserv
источник
1
Следует отметить, что с первым решением, то есть с необходимостью использования $varв { ... }группировке, не всегда возможно. Например, если эта команда выполняется внутри цикла, а нужно $varвне цикла.
vinc17
@ vinc17 - если это цикл I желательно использовать, то я хотел бы использовать его вместо {}скобок .Это верно - и явно указано в ответе - что значение $varявляется весьма вероятно , исчезнет полностью , когда { current shell; }группировка закрыто. Есть ли какой-то более явный способ сказать это, чем, что бы ты ни делал, $varисчезает ...
mikeserv
@ vinc17 - вероятно, лучший способ, однако:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv
1
Также есть _variablesфункция bash_completion, которая хранит результат подстановки команды в глобальной переменной COMPREPLY. Если бы для сохранения новых строк использовалось конвейерное решение, результат был бы потерян. В вашем ответе создается впечатление, что оба решения одинаково хороши. Кроме того, следует отметить, что поведение конвейерного решения сильно зависит от оболочки: пользователь может протестировать echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $varс помощью ksh93 и zsh и считает, что все в порядке, хотя этот код содержит ошибки.
vinc17
1
Вы не сказали "это не работает". Вы только что сказали « $varисчезает» (что на самом деле не соответствует действительности, поскольку это зависит от оболочки - поведение не определено POSIX), что является довольно нейтральным предложением. Второе решение лучше, потому что оно не страдает от этой проблемы, и его поведение одинаково во всех оболочках POSIX.
vinc17