Автоматически записывать вывод последней команды в переменную с помощью Bash?


140

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

$ find . -name foo.txt
./home/user/some/directory/foo.txt

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

mv <some-variable-that-contains-the-result> /some/new/location

Как я могу это сделать? Может быть, использовать какую-то переменную bash?

Обновить:

Чтобы уточнить, я не хочу назначать вещи вручную. Мне нужно что-то вроде встроенных переменных bash, например

ls /tmp
cd $_

$_содержит последний аргумент предыдущей команды. Я хочу нечто подобное, но с выводом последней команды.

Окончательное обновление:

Ответ Сета сработал довольно хорошо. Следует иметь в виду следующее:

  • не забудьте, touch /tmp/xкогда пробуете решение в первый раз
  • результат будет сохранен только в том случае, если код выхода последней команды был успешным

Увидев вашу правку, я решил удалить свой ответ. Интересно, есть ли что-нибудь встроенное, что вы ищете.
taskinoor

Ничего встроенного не нашел. Мне было интересно, можно ли это реализовать ... может быть, через .bahsrc? Я думаю, это была бы очень удобная функция.
armandino

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

3
Вы не можете сделать это без взаимодействия оболочки и терминала, а они обычно не взаимодействуют. См. Также Как повторно использовать последний вывод командной строки? и Использование текста из вывода предыдущих команд на Unix Stack Exchange .
Жиль 'SO- перестань быть злым'

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

Ответы:


72

Это действительно хакерское решение, но, похоже, иногда оно работает. Во время тестирования я заметил, что иногда это не очень хорошо работает при получении ^Cв командной строке, хотя я немного подправил его, чтобы вести себя немного лучше.

Этот взлом предназначен только для интерактивного режима, и я совершенно уверен, что никому его не рекомендую. Фоновые команды могут вызвать еще менее определенное поведение, чем обычно. Другие ответы - лучший способ программного получения результатов.


При этом вот «решение»:

PROMPT_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)'

Установите эту переменную окружения bash и выдает нужные команды. $LASTобычно будет вывод, который вы ищете:

startide seth> fortune
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
startide seth> echo "$LAST"
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve

1
@armandino: это, конечно, приводит к тому, что программы, ожидающие взаимодействия с терминалом в стандартном режиме, не работают должным образом (больше / меньше) или сохраняют странные вещи в $ LAST (emacs). Но я думаю, что это примерно так хорошо, как вы собираетесь получить. Единственный другой вариант - использовать (тип) скрипт для сохранения копии ВСЕГО в файл, а затем использовать PROMPT_COMMAND для проверки изменений с момента последнего PROMPT_COMMAND. Однако это будет включать в себя то, что вам не нужно. Я почти уверен, что вы не найдете ничего более близкого к тому, что вам нужно.
Сет Робертсон

2
Чтобы пойти немного дальше: PROMPT_COMMAND='last="$(cat /tmp/last)";lasterr="$(cat /tmp/lasterr)"; exec >/dev/tty; exec > >(tee /tmp/last); exec 2>/dev/tty; exec 2> >(tee /tmp/lasterr)'который предоставляет и $lastи $lasterr.
aphink

@cdosborn: man по умолчанию отправляет вывод через пейджер. Как было сказано в моем предыдущем комментарии: «программы, ожидающие взаимодействия с терминалом в стандарте, не работают должным образом (больше / меньше)». больше и меньше пейджеры.
Сет Робертсон

Я получаю:No such file or directory
Франсиско Корралес Моралес

@Raphink Я просто хотел указать на исправление: ваше предложение должно заканчиваться, 2> >(tee /tmp/lasterr 1>&2)потому что стандартный вывод teeдолжен быть перенаправлен обратно на стандартную ошибку.
Hugues

91

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

vim $(!!)

Где !!расширение истории означает «предыдущая команда».

Если вы ожидаете, что будет одно имя файла с пробелами или другими символами, которые могут помешать правильному синтаксическому анализу аргументов, укажите результат в кавычках ( vim "$(!!)"). Если оставить его без кавычек, можно будет открывать сразу несколько файлов, если они не содержат пробелов или других токенов синтаксического анализа оболочки.


1
Копирование-вставка - это то, что я обычно делаю в таких случаях, еще потому, что вам обычно нужна только часть вывода команды. Я удивлен, что вы первым упомянули об этом.
Бруно Де Фрайн

4
Вы можете сделать то же самое с меньшим количеством нажатий клавиш:vim `!!`
psmears

Чтобы увидеть отличие от принятого ответа, выполните, date "+%N"а затемecho $(!!)
Marinos An

20

Баш - некрасивый язык. Да, вы можете назначить вывод переменной

MY_VAR="$(find -name foo.txt)"
echo "$MY_VAR"

Но лучше надейтесь, что из ваших усилий findвернется только один результат и что в этом результате не будет никаких «нечетных» символов, таких как возврат каретки или перевод строки, поскольку они будут незаметно изменены при присвоении переменной Bash.

Но лучше будьте осторожны, чтобы правильно указывать вашу переменную при ее использовании!

Это лучше действовать на файл непосредственно, например , с find«s -execdir(обратитесь к руководству).

find -name foo.txt -execdir vim '{}' ';'

или

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'

5
Они не изменяются молча при назначении, они изменяются при эхо! Вам нужно только сделать это, echo "${MY_VAR}"чтобы убедиться, что это так.
paxdiablo

13

Есть несколько способов сделать это. Один из способов - использовать, v=$(command)который назначит вывод команды v. Например:

v=$(date)
echo $v

И вы также можете использовать обратные кавычки.

v=`date`
echo $v

Из руководства для начинающих по Bash ,

Когда используется форма подстановки в обратных кавычках в старом стиле, обратная косая черта сохраняет свое буквальное значение, за исключением случаев, когда за ними следует «$», «« »или« \ ». Первые обратные кавычки, которым не предшествует обратная косая черта, завершают подстановку команд. При использовании формы «$ (КОМАНДА)» все символы в круглых скобках составляют команду; никто особо не лечится.

EDIT: после редактирования вопроса кажется, что это не то, что ищет OP. Насколько мне известно, специальной переменной, как $_для вывода последней команды, нет.


11

Это довольно просто. Используйте обратные кавычки:

var=`find . -name foo.txt`

И тогда вы можете использовать это в любое время в будущем

echo $var
mv $var /somewhere

11

Дискламеры:

  • Этот ответ поздний полгода: D
  • Я заядлый пользователь tmux
  • Вы должны запустить свою оболочку в tmux, чтобы это работало

При запуске интерактивной оболочки в tmux вы можете легко получить доступ к данным, отображаемым в данный момент на терминале. Давайте посмотрим на несколько интересных команд:

  • tmux capture-pane : копирует отображаемые данные в один из внутренних буферов tmux. Он может копировать историю, которая в настоящее время не отображается, но нас это сейчас не интересует.
  • tmux list-buffers : отображает информацию о захваченных буферах. Самый новый будет иметь номер 0.
  • tmux show-buffer -b (buffer num) : выводит содержимое данного буфера на терминал.
  • tmux paste-buffer -b (buffer num) : вставляет содержимое данного буфера в качестве ввода

Да, теперь это дает нам много возможностей :) Что касается меня, я установил простой псевдоним: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1"и теперь каждый раз, когда мне нужно получить доступ к последней строке, я просто использую, $(L)чтобы ее получить.

Это не зависит от выходного потока, используемого программой (будь то stdin или stderr), метода печати (ncurses и т. Д.) И кода выхода программы - данные просто нужно отобразить.


Эй, спасибо за совет tmux. Я искал в основном то же самое, что и OP, нашел ваш комментарий и закодировал сценарий оболочки, чтобы вы могли выбрать и вставить часть вывода предыдущей команды, используя команды tmux, которые вы упомянули, и движения Vim: github.com/ bgribble / lw
Билл Гриббл

9

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

#!/bin/sh
bash | tee /var/log/bash.out.log

Затем, если вы настроили $PROMPT_COMMANDвывод разделителя, вы можете написать вспомогательную функцию (возможно, вызываемую _), которая получит последний фрагмент этого журнала, чтобы вы могли использовать его так:

% find lots*of*files
...
% echo "$(_)"
... # same output, but doesn't run the command again

7

Вы можете настроить следующий псевдоним в своем профиле bash:

alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'

Затем, набрав 's' после произвольной команды, вы можете сохранить результат в переменной оболочки 'it'.

Итак, пример использования:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'

Спасибо! Это то, что мне нужно для реализации моей grabфункции, которая копирует n-ю строку из последней команды в буфер обмена gist.github.com/davidhq/f37ac87bc77f27c5027e
davidhq

6

Я просто извлек эту bashфункцию из предложенных здесь:

grab() {     
  grab=$("$@")
  echo $grab
}

Затем вы просто делаете:

> grab date
Do 16. Feb 13:05:04 CET 2012
> echo $grab
Do 16. Feb 13:05:04 CET 2012

Обновление : анонимный пользователь предложил заменить echo, printf '%s\n'что имеет то преимущество, что он не обрабатывает параметры, как -eв захваченном тексте. Итак, если вы ожидаете или испытываете такие особенности, рассмотрите это предложение. Другой вариант - использовать cat <<<$grabвместо него.


1
Хорошо, строго говоря, это не ответ, потому что «автоматическая» часть полностью отсутствует.
Тильман Фогель

Можете объяснить cat <<<$grabвариант?
leoj

1
Цитата из man bash: «Here Strings: вариант здесь документов, формат следующий: <<<wordслово раскрывается и передается команде на ее стандартный ввод». Итак, значение $grabпередается catна стандартный ввод и catпросто возвращает его обратно на стандартный вывод.
Tilman Vogel

5

Говоря «Я хотел бы иметь возможность использовать результат последней выполненной команды в следующей команде», я предполагаю - вы имеете в виду результат любой команды, а не только поиск.

Если это так - xargs - это то, что вы ищете.

find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

ИЛИ, если вы хотите сначала увидеть результат:

find . -name foo.txt -print0

!! | xargs -0 -I{} mv {} /some/new/location/{}

Эта команда работает с несколькими файлами и работает как шарм, даже если путь и / или имя файла содержат пробелы.

Обратите внимание на часть команды mv {} / some / new / location / {} . Эта команда создается и выполняется для каждой строки, напечатанной предыдущей командой. Здесь строка, напечатанная предыдущей командой, заменяется вместо {} .

Выдержка из справочной страницы xargs:

xargs - строить и выполнять командные строки из стандартного ввода

Подробнее см. Справочную страницу: man xargs


5

Захват вывода с обратными кавычками:

output=`program arguments`
echo $output
emacs $output

4

Обычно я делаю то, что предлагают другие ... без задания:

$find . -iname '*.cpp' -print
./foo.cpp
./bar.cpp
$vi `!!`
2 files to edit

Вы можете стать красивее, если хотите:

$grep -R "some variable" * | grep -v tags
./foo/bar/xxx
./bar/foo/yyy
$vi `!!`

2

Если все, что вам нужно, это повторно запустить вашу последнюю команду и получить результат, подойдет простая переменная bash:

LAST=`!!`

Итак, вы можете запустить свою команду на выходе с помощью:

yourCommand $LAST

Это вызовет новый процесс и повторно выполнит вашу команду, а затем выдаст вам результат. Похоже, что вам действительно нужен файл истории bash для вывода команд. Это означает, что вам нужно будет захватить вывод, который bash отправляет на ваш терминал. Вы можете написать что-нибудь, чтобы следить за необходимостью / dev или / proc, но это беспорядочно. Вы также можете просто создать «специальный канал» между вашим термином и bash с помощью команды tee в середине, которая перенаправляет на ваш выходной файл.

Но оба это своего рода хакерские решения. Думаю, лучше всего будет терминатор - более современный терминал с логированием вывода. Просто проверьте свой файл журнала на предмет результатов последней команды. Переменная bash, подобная приведенной выше, сделает это еще проще.


1

Вот один из способов сделать это после того, как вы выполнили свою команду и решили, что хотите сохранить результат в переменной:

$ find . -name foo.txt
./home/user/some/directory/foo.txt
$ OUTPUT=`!!`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
$ mv $OUTPUT somewhere/else/

Или, если вы заранее знаете, что хотите получить результат в переменной, вы можете использовать обратные кавычки:

$ OUTPUT=`find . -name foo.txt`
$ echo $OUTPUT
./home/user/some/directory/foo.txt

1

В качестве альтернативы существующим ответам: используйте, whileесли имена ваших файлов могут содержать такие пробелы:

find . -name foo.txt | while IFS= read -r var; do
  echo "$var"
done

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

NB: единственная встроенная информация не о выводе, а о статусе последней команды.


1

вы можете использовать !!: 1. Пример:

~]$ ls *.~
class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 

~]$ rm !!:1
rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 


~]$ ls file_to_remove1 file_to_remove2
file_to_remove1 file_to_remove2

~]$ rm !!:1
rm file_to_remove1

~]$ rm !!:2
rm file_to_remove2

Эта нотация получает пронумерованный аргумент из команды: См. Документацию для «$ {параметр: смещение}» здесь: gnu.org/software/bash/manual/html_node/… . Смотрите также здесь, чтобы увидеть
Александр Бёрд,

1

У меня была аналогичная потребность, в которой я хотел использовать вывод последней команды в следующей. Очень похоже на | (труба). например

$ which gradle 
/usr/bin/gradle
$ ls -alrt /usr/bin/gradle

к чему-то вроде -

$ which gradle |: ls -altr {}

Решение: Создал эту кастомную трубу. Действительно просто, используя xargs -

$ alias :='xargs -I{}'

По сути, ничего, создавая короткую руку для xargs, это работает как шарм и действительно удобно. Я просто добавляю псевдоним в файл .bash_profile.


1

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

Это нужно делать с помощью скрипта - опция «lastpipe» не будет работать в интерактивном режиме.

Вот сценарий, который я тестировал:

$ cat shorttest.sh 
#!/bin/bash
shopt -s lastpipe

exit_tests() {
    EXITMSG="$(cat /proc/self/fd/0)"
}

ls /bloop 2>&1 | exit_tests

echo "My output is \"$EXITMSG\""


$ bash shorttest.sh 
My output is "ls: cannot access '/bloop': No such file or directory"

Что я здесь делаю:

  1. установка опции оболочки shopt -s lastpipe. Без этого он не будет работать, так как вы потеряете дескриптор файла.

  2. убедиться, что мой stderr также попадает в 2>&1

  3. передача вывода в функцию, чтобы можно было ссылаться на дескриптор файла stdin.

  4. установка переменной путем получения содержимого /proc/self/fd/0дескриптора файла, которым является stdin.

Я использую это для фиксации ошибок в скрипте, поэтому, если есть проблема с командой, я могу остановить обработку скрипта и сразу выйти.

shopt -s lastpipe

exit_tests() {
    MYSTUFF="$(cat /proc/self/fd/0)"
    BADLINE=$BASH_LINENO
}

error_msg () {
    echo -e "$0: line $BADLINE\n\t $MYSTUFF"
    exit 1
}

ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

Таким образом, я могу добавить 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msgкаждую команду, которую хочу проверить.

Теперь вы можете наслаждаться своим результатом!


0

Это не строго решение bash, но вы можете использовать конвейер с sed, чтобы получить последнюю строку вывода предыдущих команд.

Сначала давайте посмотрим, что у меня в папке "а"

rasjani@helruo-dhcp022206::~$ find a
a
a/foo
a/bar
a/bat
a/baz
rasjani@helruo-dhcp022206::~$ 

Затем ваш пример с ls и cd превратится в sed и piping примерно так:

rasjani@helruo-dhcp022206::~$ cd `find a |sed '$!d'`
rasjani@helruo-dhcp022206::~/a/baz$ pwd
/home/rasjani/a/baz
rasjani@helruo-dhcp022206::~/a/baz$

Итак, настоящая магия происходит с sed: вы передаете результат любой команды в sed, а sed выводит последнюю строку, которую вы можете использовать в качестве параметра с обратными тиками. Или вы также можете объединить это с xargs. ("man xargs" в оболочке - ваш друг)


0

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

Научитесь использовать символ вертикальной черты с awk.

find . | awk '{ print "FILE:" $0 }'

В приведенном выше примере вы можете:

find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'

0
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

еще один способ, хоть и грязный.


найти . -name foo.txt 1> tmpfile && mv `cat tmpfile` path / to / some / dir && rm tmpfile
Кевин

0

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

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

Достаточно просто, вставьте это в свой .bash_profile:

Сценарий

# catch stdin, pipe it to stdout and save to a file
catch () { cat - | tee /tmp/catch.out}
# print whatever output was saved to a file
res () { cat /tmp/catch.out }

Применение

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

$ res
/path/to/filename

На этом этапе я обычно просто добавляю | catchвсе свои команды в конец, потому что это не требует затрат и избавляет меня от необходимости повторно запускать команды, выполнение которых требует много времени.

Кроме того, если вы хотите открыть вывод файла в текстовом редакторе, вы можете сделать это:

# vim or whatever your favorite text editor is
$ vim <(res)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.