我有数百个经过测试和尝试的命令和片段保存在各种文本文件中。当我需要重复使用时,我会找到并复制/粘贴到命令行。尽管可靠,但这是一种笨拙的方法。与此相比,我们可以从历史文件中直接检索旧命令到命令行并准备编辑和使用。我如何模拟此功能并使用我自己的命令集?
我有脚本来搜索(使用 fzf)、查找并将我自己的命令附加到历史文件中。这效果很好。但它确实使历史文件变得混乱。
这是我的脚本:
find ~ -type f | fzf --preview-window=up:50% --header "ENTER:less; ^s:snip to history" --bind "enter:execute(less {})" --bind "ctrl-s:execute(cat {} | fzf -m --preview-window=:hidden >>~/.bash_history)"; history -n
答案1
一个用于交互式 Bash 的快速而肮脏的装置(我的bash --version
打印5.2.15
):
将您想要的(单行)命令放入
~/.commands
.定义以下辅助函数:
_get_command () { READLINE_LINE="$(<~/.commands fzf -1 --query="$READLINE_LINE")" && READLINE_POINT="${#READLINE_LINE}" }
绑定到按键(按键序列):
bind -x '"\C-x\C-r":_get_command'
开始使用Ctrl+ x, Ctrl+ r。
笔记:
READLINE_POINT="${#READLINE_LINE}"
应该将光标放在最后。 Bash 5 想要字符长度,上面的代码可以工作;但 Bash 4 需要字节。要在 Bash 4 中处理非 ASCII 文本,请使用READLINE_POINT="$(printf '%s' "$READLINE_LINE" | wc -c)"
(我从这个答案)。
因为
~/.commands
您可以发明自己的格式来指定或编码每个命令所需的光标位置。改进帮助程序功能以根据您的格式READLINE_LINE
进行设置。READLINE_POINT
另一方面,
~/.commands
仅存储文字命令是简单的并且可以很好地与--query="$READLINE_LINE"
.对于多行命令,您需要
fzf --read0
和 中的空终止条目~/.commands
。维护这种格式比编辑换行符终止的条目更困难。
写完上面的内容后,我决定想要这个功能。这是我更复杂的解决方案(放入~/.bashrc
):
export SNIPPETFILE
_del_snippet () {
SNIPPETFILE="${SNIPPETFILE:-"$HOME/.snippets"}"
printf '%s\0' "$1" | sort -zm -- "$SNIPPETFILE" - | uniq -zu >"$SNIPPETFILE"~ \
&& mv -- "$SNIPPETFILE"~ "$SNIPPETFILE"
cat -- "$SNIPPETFILE"
}
export -f _del_snippet
_get_snippet () {
SNIPPETFILE="${SNIPPETFILE:-"$HOME/.snippets"}"
local line
line="$(<"$SNIPPETFILE" fzf -1 --read0 --query="$READLINE_LINE" \
--border top --border-label 'Enter to confirm, Ctrl+y to delete entry, Ctrl+c to abort' \
--bind 'ctrl-y:reload-sync(exec bash -c "_del_snippet \"\$1\"" bash {})')" \
&& READLINE_LINE="$line" \
&& READLINE_POINT="${#READLINE_LINE}"
}
bind -x '"\C-x\C-r":_get_snippet'
_put_snippet () {
[[ "$READLINE_LINE" =~ ^[[:space:]]*$ ]] && return 0
SNIPPETFILE="${SNIPPETFILE:-"$HOME/.snippets"}"
if
touch -- "$SNIPPETFILE" \
&& printf '%s\0' "$READLINE_LINE" | sort -zum -- "$SNIPPETFILE" - >"$SNIPPETFILE"~ \
&& mv -- "$SNIPPETFILE"~ "$SNIPPETFILE"
then
printf 'command stored in `%s'\''\n' "$SNIPPETFILE" >&2
else
printf 'storing in `%s'\'' failed\n' "$SNIPPETFILE" >&2
return 1
fi
}
bind -x '"\C-x\C-m":_put_snippet'
用法:
Ctrl使用+ x, Ctrl+存储当前命令行m。
使用Ctrl+ x、Ctrl+r从存储的命令列表中进行选择。如果有一个与您已经输入的内容完全匹配,它将被带到命令行。如果还有更多,您将可以交互选择。
交互选择时,Ctrl+y会删除突出显示的条目。
笔记:
支持多行命令,但为了正确存储它们,您不能点击Enter(引号内或之后
\
),否则会出现辅助提示。您需要从历史记录中引入多行命令,或者通过按Ctrl+ v、Ctrl+使命令成为多行j,这样就不会出现辅助提示,您仍然可以编辑整个命令。然后Ctrl+ x, Ctrl+m就能存储整个多行命令。当您以交互方式选择这些行时,它们看起来不像多行命令,但它们将作为多行带到命令行。
这会产生一个很好的副作用:您可以存储带标签的命令。这个
# description (label) here : actual_command args
在列表中将如下所示:
# description (label) here : actual_command args
但在被选择之后它就会起作用。
默认文件是
$HOME/.snippets
.您可以通过设置并export
使用名为 的变量来使用另一个路径名SNIPPETFILE
。带有后置波浪号的路径名(例如$HOME/.snippets~
)也将用于维护。在 Bash 5.2.15 中使用 GNU
sort
和 GNU进行测试uniq
(使用非可移植选项)。可能仍然存在错误。
答案2
可能值得探索 xdotool 来解决您的问题。
根据手册页:
xdotool 允许您以编程方式(或手动)模拟键盘输入和鼠标活动、移动窗口和调整窗口大小等。
因此,您可以定义一个别名来帮助您找到您最喜欢的命令并告诉 xdotool 将其放置在命令行上。例如:
$ alias my_command='stty -echo && xdotool type "$(cat any_list | fzf)" ; stty echo'
这将允许您从文件“any_list”中提取任何文本行并将其放置在命令行上。如果该文本恰好是您的旧命令之一,那么您就可以编辑并执行它了。
您当前的设置中可能已经有 xdotool。如果没有,那么下载并安装它非常容易——例如https://installati.one/install-xdotool-debian-12
答案3
在bash
,shell-expand-line
(键盘快捷键M-C-e
,通常为ALT+ CTRL+ e)将“像 shell 一样扩展行。这将执行别名和历史扩展以及所有 shell 单词扩展”。
因此,实现您想要的最简单的方法是将要插入命令行的命令分配给某个变量,写入变量的名称,然后按此按键序列以展开其内容。
例如:
$ COMMAND="echo hello world"
现在在命令行中写入变量的名称($COMMAND
在本例中),然后按ALT+ CTRL+ e。$COMMAND
将自动在当前行中替换为其内容。
$ echo hello world
$(!!)
或者,您可以通过编写然后单击序列,将命令行替换为上一个命令的输出。例如:
$ grep ps .snippets
ps -fp $$
$ $(!!)
在该点单击ALT+ CTRL+e会将命令行替换为命令的输出grep
。
$ ps -fp $$