Повторное использование вывода из последней команды в Bash


180

Сохраняется ли вывод команды Bash в каком-либо регистре? Например, что-то похожее на $?захват вывода вместо состояния выхода.

Я мог бы назначить вывод переменной:

output=$(command)

но это больше печатать ...


Ответы:


185

Вы можете использовать $(!!) для пересчета (не повторного использования) вывод последней команды.

Сам !!по себе выполняет последнюю команду.

$ echo pierre
pierre
$ echo my name is $(!!)
echo my name is $(echo pierre)
my name is pierre

9
Вы можете уберечь себя от набора текста $(your_cmd)с помощью обратных галочек: `your_cmd`согласно руководству Gnu Bash они функционально идентичны. Конечно, это также зависит от предупреждения @memecs

На практике вывод команд является детерминированным, поэтому я не буду мучиться из-за повторного вычисления. Это, безусловно, полезно, когда вы хотите сделать что-то вроде того, git diff $(!!)где предыдущая команда была findвызовом.
Шридхар Сарнобат

Видимо, есть веские причины, чтобы использовать $()вместо обратной галочки. Но, наверное, нечего терять сон. github.com/koalaman/shellcheck/wiki/SC2006
Майкл Креншоу

1
@ user2065875, хотя обратные пометки все еще работают в Bash, они устарели в пользу $().
Курчинский

87

Ответ - нет. Bash не выделяет никакого вывода ни одному параметру или любому блоку в своей памяти. Кроме того, вам разрешен доступ к Bash только с помощью разрешенных операций интерфейса. Личные данные Bash не доступны, если вы не взломаете их.


2
Возможно ли иметь какую-то пользовательскую функцию промежуточного программного обеспечения bash, которая запускается перед каждой командой, где все, что она делает, это сохраняет полученный результат во что-то, что действует как внутренний буфер обмена (скажем, BASH_CLIPBOARD)?
Пэт Нидхэм

@PatNeedham Не уверен. Проверьте загружаемые модули. Посмотри, сможешь ли ты написать один.
konsolebox

12

Один из способов сделать это с помощью trap DEBUG:

f() { bash -c "$BASH_COMMAND" >& /tmp/out.log; }
trap 'f' DEBUG

Теперь stdout и stderr последней выполненной команды будут доступны в /tmp/out.log

Единственным недостатком является то, что он будет выполнять команду дважды: один раз для перенаправления вывода и ошибок /tmp/out.logи один раз в обычном режиме. Возможно, есть какой-то способ предотвратить такое поведение.


10
поэтому, когда я набираю rm -rf * && cd .., не только текущий каталог, но и родительский каталог будет удален ?? этот подход кажется мне очень опасным
phil294

1
Теперь, как мне отменить это действие?
Картик Чаухан

5
@KartikChauhan: Просто сделайtrap '' DEBUG
анубхава

7

Если вы работаете на Mac и не против сохранить результаты в буфере обмена вместо записи в переменную, вы можете использовать pbcopy и pbpaste в качестве обходного пути.

Например, вместо того, чтобы сделать это, чтобы найти файл и сравнить его содержимое с другим файлом:

$ find app -name 'one.php' 
/var/bar/app/one.php

$ diff /var/bar/app/one.php /var/bar/two.php

Вы могли бы сделать это:

$ find app -name 'one.php' | pbcopy
$ diff $(pbpaste) /var/bar/two.php

Строка /var/bar/app/one.phpнаходится в буфере обмена при запуске первой команды.

Кстати, pb in pbcopyи pbpasteрасшифровывается как pasteboard, синоним буфера обмена.


1
на Linux $ find app -name 'one.php' | xclip $ diff $(xclip -o) /var/bar/two.php
Си-Мики

Интересно, я никогда не думал об использовании pbpasteвместе с подстановкой команд.
Шридхар Сарнобат

Что, если я сделаю первый и позже пойму, что это было неэффективно, как мне сэкономить вычислительное время без повторного запуска со вторым вариантом?
НельсонГон

4

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

save_output() { 
   exec 1>&3 
   { [ -f /tmp/current ] && mv /tmp/current /tmp/last; }
   exec > >(tee /tmp/current)
}

exec 3>&1
trap save_output DEBUG

Таким образом, выходные данные последней команды находятся в / tmp / last, и команда не вызывается дважды.


1
Один недостаток, который я обнаружил, заключается в том, что ни одна из ваших команд не думает, что они больше работают в tty, а это значит, что вам нужно особое внимание, чтобы коды цвета работали для таких утилит, как grepили git diff+1, в любом случае
Марти Нил

2

Как сказал konsolebox, вам придется взломать сам bash. Вот довольно хороший пример того, как можно этого достичь. Репозиторий stderred (на самом деле предназначен для окраски стандартного вывода) дает инструкции о том, как его построить.

Я дал ему попробовать: определение некоторого нового файлового дескриптора внутри, .bashrcкак

exec 41>/tmp/my_console_log

(число произвольно) и измените его stderred.cсоответствующим образом, чтобы содержимое также записывалось на fd 41. Это вроде работает, но содержит множество байтов NUL, странное форматирование и в основном представляет собой двоичные данные, которые не читаются. Может быть, кто-то с хорошим пониманием C мог бы попробовать это.

Если это так, все, что нужно для получения последней напечатанной строки tail -n 1 [logfile].


1

Да, почему каждый раз соглашались набирать лишние строки. Вы можете перенаправить возвращенный на вход, но выход на вход (1> & 0) - нет. Также вы не захотите писать функцию снова и снова в каждом файле для одного и того же.

Если вы нигде не экспортируете файлы и намереваетесь использовать их только локально, вы можете настроить Терминал на объявление функции. Вы должны добавить функцию в ~/.bashrcфайл или в ~/.profileфайл. Во втором случае вам нужно включить Run command as login shellс Edit>Preferences>yourProfile>Command.

Сделайте простую функцию, скажем:

get_prev()
{
    # option 1: create an executable with the command(s) and run it
    echo $* > /tmp/exe
    bash /tmp/exe > /tmp/out

    # option 2: if your command is single command (no-pipe, no semi-colons), still it may not run correct in some exceptions
    #echo `$*` > /tmp/out

    # return the command(s) outputs line by line
    IFS=$(echo -en "\n\b")
    arr=()
    exec 3</tmp/out
    while read -u 3 -r line
    do
        arr+=($line)
        echo $line
    done
    exec 3<&-
}

В основном скрипте:

#run your command:
cmd="echo hey ya; echo hey hi; printf `expr 10 + 10`'\n' ; printf $((10 + 20))'\n'"
get_prev $cmd
#or simply
get_prev "echo hey ya; echo hey hi; printf `expr 10 + 10`'\n' ; printf $((10 + 20))'\n'"

Bash сохраняет переменную даже за пределами предыдущей области видимости:

#get previous command outputs in arr
for((i=0; i<${#arr[@]}; i++)); do echo ${arr[i]}; done

1

Очень простое решение

Тот, который я использовал в течение многих лет.

Скрипт (добавить в свой .bashrcили .bash_profile)

# capture the output of a command so it can be retrieved with ret
cap () { tee /tmp/capture.out}

# return the output of the most recent command that was captured by cap
ret () { cat /tmp/capture.out }

использование

$ find . -name 'filename' | cap
/path/to/filename

$ ret
/path/to/filename

Я склонен добавлять | capв конец всех моих команд. Таким образом, когда я нахожу, что хочу выполнить обработку текста на выходе медленной команды, я всегда могу получить ее res.


Что такое точка cat -в cap () { cat - | tee /tmp/capture.out}здесь? Есть ли cap () { tee /tmp/capture.out }брать на себя какую - либо икоту я пропускаю?
Уотсон

1
@ Уотсон, хорошая мысль! Я не знаю, почему у меня это там, вероятно, привычка. Должен ли я добавить cat - | cat - | cat -заранее, чтобы сделать его более интересным? Fix Я исправлю это, как только протестирую, чтобы убедиться, что это действительно спандрел
Коннор

0

Не уверен, что именно вам это нужно, поэтому этот ответ может быть неактуальным. Вы всегда можете сохранить выходные данные команды:, netstat >> output.txtно я не думаю, что это то, что вы ищете.

Хотя, конечно, есть варианты программирования; вы могли бы просто заставить программу прочитать приведенный выше текстовый файл после выполнения этой команды и связать ее с переменной, а в Ruby, моем предпочтительном языке, вы можете создать переменную из вывода команды, используя 'backticks':

output = `ls`                       #(this is a comment) create variable out of command

if output.include? "Downloads"      #if statement to see if command includes 'Downloads' folder
print "there appears to be a folder named downloads in this directory."
else
print "there is no directory called downloads in this file."
end

Вставьте это в файл .rb и запустите его: ruby file.rbи он создаст переменную из команды и позволит вам манипулировать ею.


9
«Не знаю точно, для чего вам это нужно» Ну, скажем, вы (под этим я имею в виду меня ) просто запустили сценарий большой продолжительности, хотите найти выходные данные и тупо забыли перенаправить выходные данные перед выполнением. Теперь я хочу попытаться исправить мою глупость без повторного запуска сценария.
ОАО

0

У меня есть идея, что у меня нет времени, чтобы попытаться реализовать немедленно.

Но что, если вы сделаете что-то вроде следующего:

$ MY_HISTORY_FILE = `get_temp_filename`
$ MY_HISTORY_FILE=$MY_HISTORY_FILE bash -i 2>&1 | tee $MY_HISTORY_FILE
$ some_command
$ cat $MY_HISTORY_FILE
$ # ^You'll want to filter that down in practice!

Могут быть проблемы с буферизацией ввода-вывода. Также файл может стать слишком большим. Нужно было бы найти решение этих проблем.


0

Я думаю, что использование команды сценария может помочь. Что-то вроде,

  1. скрипт -c bash -qf fifo_pid

  2. Использование функций bash для установки после разбора.


0

Если вы не хотите пересчитывать предыдущую команду, вы можете создать макрос, который сканирует текущий буфер терминала, пытается угадать вывод -suppposed- последней команды, копирует его в буфер обмена и, наконец, вводит его в терминал.

Его можно использовать для простых команд, которые возвращают одну строку вывода (протестировано в Ubuntu 18.04 с gnome-terminal).

Установите следующие инструменты: xdootool, xclip,ruby

В gnome-terminalиди Preferences -> Shortcuts -> Select allи установите его Ctrl+shift+a.

Создайте следующий скрипт ruby:

cat >${HOME}/parse.rb <<EOF
#!/usr/bin/ruby
stdin = STDIN.read
d = stdin.split(/\n/)
e = d.reverse
f = e.drop_while { |item| item == "" }
g = f.drop_while { |item| item.start_with? "${USER}@" }
h = g[0] 
print h
EOF

В настройках клавиатуры добавьте следующее сочетание клавиш:

bash -c '/bin/sleep 0.3 ; xdotool key ctrl+shift+a ; xdotool key ctrl+shift+c ; ( (xclip -out | ${HOME}/parse.rb ) > /tmp/clipboard ) ; (cat /tmp/clipboard | xclip -sel clip ) ; xdotool key ctrl+shift+v '

Вышеуказанный ярлык:

  • копирует текущий буфер терминала в буфер обмена
  • извлекает вывод последней команды (только одна строка)
  • введите его в текущий терминал
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.