Автоматично фіксувати вихід останньої команди в змінну за допомогою Bash?


139

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

$ 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? Я думаю, це було б дуже зручною функцією.
армандіно

Я боюсь, що все, що ви можете зробити, це або перенаправити вихідний файл у файл або на трубу, або захопити його, інакше це не буде збережено.
Банді

3
Ви не можете зробити це без співпраці оболонки та терміналу, і вони, як правило, не співпрацюють. Див. Також Як я повторно використовую останній висновок з командного рядка? та Використання тексту з результатів попередніх команд на Unix Stack Exchange .
Жил "ТАК - перестань бути злим"

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

Відповіді:


71

Це справді хакітне рішення, але, здається, в основному працює деякий час. Під час тестування я зазначив, що іноді це не дуже добре спрацьовує при введенні в ^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.
phaphink

@cdosborn: людина за замовчуванням надсилає вихід через пейджер. Як говорив мій попередній коментар: "програми, які очікують взаємодії з терміналом на стандартному виході, не працюватимуть, як очікувалося (більше / менше)". все більше і менше є пейджерами.
Сет Робертсон

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

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

90

Я не знаю жодної змінної, яка робить це автоматично . Щоб зробити щось окрім просто копіювання результату, ви можете запустити все, що тільки що зробили, наприклад

vim $(!!)

Де !!розширення історії означає "попередня команда".

Якщо ви очікуєте, що в ньому буде одне ім'я файлу з пробілами або іншими символами, що може перешкодити правильному аналізу аргументів, зазначайте результат ( vim "$(!!)"). Залишаючи його без котирування, це дозволить відкрити кілька файлів одночасно, доки вони не містять пробілів та інших маркерів для розбору оболонок.


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

4
Ви можете зробити те ж саме, vim `!!`
зменшивши

Щоб побачити відмінність від прийнятої відповіді, виконайте, date "+%N"а потімecho $(!!)
Марінос

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 ,

Коли використовується форма заміни, що надається в старому стилі, зворотна косої риси зберігає своє буквальне значення, за винятком випадків, коли слідують "$", "` "або" \ ". Перші зворотні посилання, яким не передує зворотний ривок, завершують підстановку команди. При використанні форми "$ (COMMAND)" всі символи між дужками складають команду; ніхто не лікується спеціально.

EDIT: Після редагування у питанні здається, що це не те, що шукає ОП. Наскільки я знаю, немає спеціальної змінної, як $_для виведення останньої команди.


11

Це досить просто. Використовуйте зворотні лапки:

var=`find . -name foo.txt`

І тоді ви можете використовувати це будь-коли в майбутньому

echo $var
mv $var /somewhere

11

Відмова від відповідальності:

  • Ця відповідь пізно на півроку: D
  • Я важкий користувач tmux
  • Для цього вам потрібно запустити свою оболонку в tmux

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

  • панель захоплення tmux : ця копіює відображені дані в один із внутрішніх буферів tmux. Він може скопіювати історію, яка наразі не видно, але нас це зараз не цікавить
  • tmux list-buffers : тут відображається інформація про захоплені буфери. Найновіший матиме число 0.
  • tmux show-buffer -b (число буфера) : це друкує вміст даного буфера на терміналі
  • tmux paste-buffer -b (число буфера) : це вставляє вміст даного буфера як вхідний

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

Це не залежить від вихідного потоку, який використовує програма (будь то stdin або stderr), способу друку (ncurses тощо) та коду виходу програми - дані просто потрібно відобразити.


Гей, спасибі за цю підказку tmux. Я шукав в основному те саме, що і ОП, знайшов ваш коментар і зашифрував сценарій оболонки, щоб дозволити вам вибрати та вставити частину виводу попередньої команди, використовуючи вказані вами команди tmux та Vim motions
Білл Гріббл

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

Ви можете встановити наступний псевдонім у своєму баш-профілі:

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: "Тут рядки: Варіант тут документів, формат: <<<wordСлово розгорнуте і подається команді на його стандартному вході." Отже, значення $grabподається catна stdin, і catпросто повертає його назад до stdout.
Тільман Фогель

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 / {} . Ця команда будується та виконується для кожного рядка, надрукованого попередньою командою. Тут рядок, надрукований попередньою командою, замінюється замість {} .

Уривок із man сторінки сторінки xargs:

xargs - створення та виконання командних рядків зі стандартного вводу

Більш детально див. Сторінку man: 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 надсилає до вашого терміналу. Ви можете написати щось для перегляду необхідного / dev або / proc, але це безладно. Ви також можете просто створити "спеціальну трубу" між вашим терміном і bash з командою tee в середині, яка переспрямовує на ваш вихідний файл.

Але обидва - це своєрідні хиткі рішення. Я думаю, що найкраще було б термінатор, який є більш сучасним терміналом з реєстрацією вихідних даних. Просто перевірте свій файл журналу на результати останньої команди. Змінна баш, схожа на вище, зробить це ще простіше.


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

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

Зверніть увагу: єдиний вбудований матеріал - це не про вихід, а про стан останньої команди.


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/… . Дивіться також тут, щоб отримати більше прикладів: howtogeek.com/howto/44997/…
Олександр Птах

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

Це не суворо баш-рішення, але ви можете використовувати трубопроводи з sed, щоб отримати останній ряд попередніх команд.

Спочатку давайте подивимось, що я маю в папці "a"

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` шлях / до / деякого / 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.