簡體   English   中英

使用 Bash 自動將上一個命令的輸出捕獲到變量中?

[英]Automatically capture output of last command into a variable using Bash?

我希望能夠在后續命令中使用上次執行命令的結果。 例如,

$ 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
  • 僅當最后一個命令的退出代碼成功時才會存​​儲結果

我不知道任何自動執行此操作的變量。 除了只是復制粘貼結果之外,你還可以重新運行你剛剛做的任何事情,例如

vim $(!!)

哪里!! 是歷史擴展,意思是“上一個命令”。

如果您希望有一個包含空格或其他字符的單個文件名,可能會阻止正確的參數解析,請引用結果( vim "$(!!)" )。 不加引號將允許一次打開多個文件,只要它們不包含空格或其他 shell 解析標記。

這是一個非常hacky的解決方案,但它似乎在某些時候大部分時間都有效。 在測試期間,我注意到在命令行上獲取^C時它有時不能很好地工作,盡管我確實對其進行了一些調整以使其表現得更好一些。

這個 hack 只是一個交互模式的 hack,我非常有信心我不會向任何人推薦它。 后台命令可能會導致比正常情況更不明確的行為。 其他答案是以編程方式獲得結果的更好方法。


話雖如此,這是“解決方案”:

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

Bash 是一種丑陋的語言。 是的,您可以將輸出分配給變量

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

但最好希望你最難 find結果只返回一個結果,並且該結果中沒有任何“奇數”字符,例如回車或換行符,因為它們在分配給 Bash 變量時會被靜默修改。

但是在使用變量時最好小心正確引用它!

最好直接對文件進行操作,例如使用find-execdir (請參閱手冊)。

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

或者

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

有不止一種方法可以做到這一點。 一種方法是使用v=$(command) ,它將命令的輸出分配給v 例如:

v=$(date)
echo $v

你也可以使用反引號。

v=`date`
echo $v

來自Bash 初學者指南

當使用舊式反引號替換形式時,反斜杠保留其字面意義,除非后跟“$”、“`”或“\\”。 前面沒有反斜杠的第一個反引號終止命令替換。 使用“$(COMMAND)”形式時,括號之間的所有字符組成命令; 沒有被特殊對待。

編輯:在問題中進行編輯后,似乎這不是 OP 正在尋找的東西。 據我所知,最后一個命令的輸出沒有像$_這樣的特殊變量。

免責聲明:

  • 這個回答晚了半年 :D
  • 我是一個重度 tmux 用戶
  • 你必須在 tmux 中運行你的 shell 才能工作

在 tmux 中運行交互式 shell 時,您可以輕松訪問當前顯示在終端上的數據。 讓我們來看看一些有趣的命令:

  • 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 等)和程序的退出代碼無關 - 只需要顯示數據。

這很容易。 使用反引號:

var=`find . -name foo.txt`

然后你可以在未來任何時候使用它

echo $var
mv $var /somewhere

我認為您可以破解一個解決方案,該解決方案涉及將您的 shell 設置為包含以下內容的腳本:

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

然后,如果您將$PROMPT_COMMAND設置$PROMPT_COMMAND輸出分隔符,您可以編寫一個輔助函數(可能稱為_ )來獲取該日志的最后一個塊,因此您可以像這樣使用它:

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

您可以在 bash 配置文件中設置以下別名:

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

然后,通過在任意命令后鍵入“s”,您可以將結果保存到 shell 變量“it”中。

所以示例用法是:

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

使用反引號捕獲輸出:

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

我只是從這里的建議中提取了這個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

更新:一位匿名用戶建議用printf '%s\\n'替換echo ,其優點是它不處理抓取文本中的-e選項。 因此,如果您期望或遇到此類特殊情況,請考慮此建議。 另一種選擇是使用cat <<<$grab代替。

通過說“我希望能夠在后續命令中使用上次執行的命令的結果”,我假設 - 您的意思是任何命令的結果,而不僅僅是查找。

如果是這樣的話 - 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

我通常會按照其他人的建議去做……沒有分配:

$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 `!!`

如果您只想重新運行上一個命令並獲取輸出,則可以使用一個簡單的 bash 變量:

LAST=`!!`

那么你可以在輸出上運行你的命令:

yourCommand $LAST

這將產生一個新進程並重新運行您的命令,然后為您提供輸出。 聽起來您真正想要的是用於命令輸出的 bash 歷史文件。 這意味着您需要捕獲 bash 發送到終端的輸出。 您可以編寫一些內容來觀察必要的 /dev 或 /proc,但這很麻煩。 你也可以在你的 term 和 bash 之間創建一個“特殊管道”,中間有一個 tee 命令,它重定向到你的輸出文件。

但這兩者都是一種hacky解決方案。 我認為最好的方法是terminator ,它是一個更現代的帶有輸出日志記錄的終端。 只需檢查您的日志文件以獲取上一條命令的結果。 類似於上述的 bash 變量會使這更簡單。

你可以使用!!: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

我有類似的需求,我想將上一個命令的輸出用於下一個命令。 很像 | (管道)。 例如

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

像 -

$ which gradle |: ls -altr {}

解決方案:創建此自定義管道。 真的很簡單,使用 xargs -

$ alias :='xargs -I{}'

通過為 xargs 創建一個簡寫基本上沒有什么,它就像魅力一樣,而且非常方便。 我只是在 .bash_profile 文件中添加別名。

可以使用文件描述符的魔法和lastpipe shell 選項來完成。

它必須通過腳本來完成——“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. 設置 shell 選項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 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg在我想檢查的每個命令后面。

現在你可以享受你的輸出了!

這是在您執行命令並決定要將結果存儲在變量中后的一種方法:

$ 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

作為現有答案的替代方案:如果您的文件名可以包含這樣的空格,請使用while

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

正如我所寫的,只有當您必須期望文件名中有空格時,差異才有意義。

注意:唯一的內置內容不是關於輸出,而是關於最后一個命令的狀態。

我發現記住將我的命令的輸出通過管道傳輸到特定文件有點煩人,我的解決方案是我的.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 | catch我所有命令的末尾,因為這樣做沒有任何成本,而且它使我不必重新運行需要很長時間才能完成的命令。

此外,如果您想在文本編輯器中打開文件輸出,您可以執行以下操作:

# vim or whatever your favorite text editor is
$ vim <(res)

這不是嚴格意義上的 bash 解決方案,但您可以使用帶有 sed 的管道來獲取先前命令輸出的最后一行。

首先讓我們看看我在文件夾“a”中有什么

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

然后,您使用 ls 和 cd 的示例將變成 sed 和管道,如下所示:

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 結合使用。 (shell 中的“man xargs”是你的朋友)

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

是另一種方式,雖然很臟。

shell 沒有類似 perl 的特殊符號來存儲最后一個命令的 echo 結果。

學習在 awk 中使用管道符號。

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

在上面的示例中,您可以執行以下操作:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM