使用 Bash 以程式方式打開新終端並執行命令,保持作業控制
在 X 會話中,我可以按照以下步驟操作:
- 打開終端仿真器 (Xterm)。
- Bash 讀取
.bashrc
並變得互動式。命令提示符正在等待命令。
- 輸入
vim 'my|file*' '!another file&'
。
- Vim 開始,帶有
my|file*
和!another file&
被編輯。
- 按
CTRL-Z
。
- Vim 成為一個暫停的作業,Bash 提示符再次出現。
如果不放棄第 3 步(作業控制) ,我無法找出執行第 1 步和第 2 步**的腳本。**它將接收文件作為參數:
script 'my|file*' '!another file&'
你能幫我麼?
該腳本將由文件管理器執行,選定的文本文件作為參數提供。
別擔心,我很清醒,通常不會這樣命名我的文件。另一方面,如果此類特殊字元 (
*!&|$<newline>...
) 碰巧出現在文件名中,則腳本不應中斷。我使用 Vim 只是一個具體的例子。在終端中以互動方式執行並接收參數的其他程序將從解決方案中受益。
我的嘗試/研究
xterm -e vim "$@"
顯然失敗了。Xterm 沒有外殼。
使用初始命令執行互動式 bash 子shell 而不返回超級 shell 立即 看起來很有希望。那裡的答案解釋瞭如何
.bashrc
為 Bash 指定不同的文件(而不是 )來獲取源。所以我創建了這個~/.vimbashrc
:. ~/.bashrc set -m vim
現在,打電話
xterm -e bash --init-file ~/.vimbashrc
產生一個帶有 Bash 和可暫停 Vim 的新終端。但是這樣我看不到如何指定 Vim 應該打開的文件。
我可以想到幾種方法,我認為第一種方法在 Bash 上不那麼駭人聽聞,主要是因為(對我而言)似乎有一些更容易處理的怪癖。但是,由於這也可能是一個口味問題,我將兩者都介紹。
方法一
“預報價”方式
它包括讓你的腳本擴展它的
$@
數組,從而代表內部互動bash
,並且你可以使用/dev/fd/X
file-descriptor-on-filesystem 工具(如果在你的系統上可用)作為參數的--init-file
參數。這樣的文件描述符可能會引用一個 Here 字元串,您將在其中處理文件名,如下所示:xterm -e bash --init-file /dev/fd/3 3<<<". ~/.bashrc; set -m; vim $@"
這個文件系統文件描述符技巧的一個好處是您有一個獨立的解決方案,因為它不依賴於外部幫助文件,例如您的
.vimbashrc
. 這在這里特別方便,因為--init-file
由於擴展,內容是動態的$@
。另一方面,文件描述符從腳本一直到內殼的實際持久性可能有一個警告。只要沒有中間程序關閉它從父程序接收到的文件描述符,這個技巧就可以正常工作。這在許多標準工具中是常見的行為,但如果
sudo
它處於關閉所有接收到的文件描述符的預設行為的中間,那麼這個技巧將不起作用,我們需要求助於臨時文件或您的原始.vimbashrc
文件。無論如何,
$@
在包含空格或換行符的文件名的情況下,簡單地使用上述方法將不起作用,因為在內部bash
使用命令序列時,這些文件名沒有被引用,因此文件名中的空格被解釋為按照標準行為的單詞分隔符。為了解決這個問題,我們可以注入一層引用,在 Bash 的 4.4 及更高版本上,只需
@Q
在數組上使用參數轉換語法$@
,如下所示:xterm -e bash --init-file /dev/fd/3 3<<<". ~/.bashrc; set -m; vim ${@@Q}"
在低於 4.4 的 Bash 版本中,我們可以通過使用它來獲得相同的結果
printf %q
,就像這樣(為了更好的可讀性,作為 Here Document,但與上面的 Here String 一樣):printf -v files ' %q' "$@" xterm -e bash --init-file /dev/fd/3 3<<EOF . ~/.bashrc set -m vim $files EOF
附帶說明一下,根據您的設置,您也可以考慮
/etc/bash.bashrc
在使用者之前進行採購.bashrc
,因為這是 Bash 對互動式 shell 的標準行為。另請注意,我將
set -m
命令留給您的原始腳本是為了熟悉,因為它是無害的,但在這裡它實際上是多餘的,因為它--init-file
暗示了一個互動式bash
,它暗示-m
. 相反,如果從字面上看問題的標題,您希望有一個工作控制外殼而不是一個完全互動式的外殼,那麼它就需要了。行為上有差異。方法二
-s
選項_Bash 的(和 POSIX)
-s
選項允許您為互動式 shell 指定參數,就像對非互動式 shell 所做的那樣1。因此,通過使用這個-s
選項,並且仍然作為一個獨立的解決方案,它會像:xterm -e bash --init-file /dev/fd/3 -s "$@" 3<<'EOF' . ~/.bashrc set -m # superfluous as bash is run with `--init-file`; you would instead need it for a job-controlling yet "non-interactive" bash (ie no `--init-file` nor `-i` option) exec <<<'exec < /dev/tty; vim "$@"' EOF
需要注意的古怪事項:
- Here Document 的分隔符規範必須在單引號內,否則
$@
Here Document 內的部分將由您的腳本擴展(沒有正確的引用等),而不是由bash
它所屬的內部擴展。這與故意不引用 Here 文件的分隔符的“預引用”方法相反- Here String (
exec <<<...
stdin 重定向片段)也必須是單引號類型2,否則其中的片段將在其數組尚未填充時"$@"
由內部擴展bash``$@
- 具體來說,我們需要這樣的標準輸入重定向(通過
exec <<<
Here 字元串進行的重定向)作為幫助程序,只是為了使內部bash
“延遲”需要完全填充$@
數組的命令的執行- 在這樣的輔助標準輸入重定向(Here String 片段)中,我們需要再次使內部
bash
重定向它自己的標準輸入,這次回到它的控制終端(因此是該exec < /dev/tty
行)以使其恢復其互動功能- 我們需要在與 the 相同的第3行指定要在(即 here)之後執行的所有命令,因為在這種重定向之後, Here String 將不再被讀取4。實際上,如果它可以像本例中那樣足夠短,那麼這個特定的片段作為 Here String 看起來會更好
exec < /dev/tty``vim "$@"``exec < /dev/tty
這種方法可能更好地與像您這樣的外部幫助文件一起使用
.vimbashrc
(儘管放棄了自包含的便利),因為關於文件名參數問題,此類文件的內容可以完全是靜態的。這樣,文件管理器呼叫的腳本將變得如此簡單:xterm -e bash --init-file .vimbashrc -s "$@"
它的同伴
.vimbashrc
會像:. ~/.bashrc # (let's do away with the superfluous `set -m`) exec <<<'exec < /dev/tty && vim "$@"' # let's also run vim only if the redirection to the controlling tty succeeded
伴隨文件仍然有怪癖,但除了任何“清潔”考慮之外,後一個版本(非獨立)的一個可能的好處是它的整個
xterm -e ...
命令,除了"$@"
部分之外,可以由您的文件管理器直接使用代替您的“腳本”,如果它允許您指定一個命令,它會盡職地在空格上分割以生成規範的“argv”數組以及文件名參數。
-s
另請注意,在所有版本中,整個方法預設使用輔助.bash_history
標準輸入重定向中指定的命令更新使用者的,這就是為什麼我一直保持該特定部分盡可能重要的原因。當然,您可以使用您喜歡的方式來防止此類更新,例如通過添加.unset HISTFILE``--init-file
–
作為比較,使用這種方法
dash
會方便得多,因為它會為環境變數指定的腳本dash
填充數組,因此一個獨立的解決方案將非常簡單:$@``ENV
xterm -e env ENV=/dev/fd/3 dash -s "$@" 3<<'EOF' # `dash` run through `env` just to be positive that `ENV` is present when dash starts vim "$@" EOF
高溫高壓
1除了無法指定的異常,與使用選項呼叫 shell 時相反
$0``-c
2如果您使用了額外的 Here Document,則還需要引用其分隔符
3實際上在POSIX 定義的相同“complete_command”上,這意味著只要它們是相同“complete_command”的一部分,您仍然可以跨越多行,例如,當這些行具有行繼續反斜杠或在復合塊內時
4這應該是標準行為,可能源自本小節的第一個簡短段落