将当前目录保存到bash历史记录中

68

我希望在历史记录中保存每个命令被执行时的当前目录。为了不搞砸事情,我考虑在每行命令结尾添加当前目录作为注释。以下是一个例子:

$ cd /usr/local/wherever
$ grep timmy accounts.txt

我希望bash能够保存最后一条命令为:

grep timmy accounts.txt # /usr/local/wherever

这个想法是这样的,我可以立即看到我发出命令的位置。

9个回答

54

一行版本

这是原始的一行版本。我也发布了短函数版本长函数版本,并添加了几个功能。我喜欢函数版本,因为它们不会干扰环境中的其他变量,并且比一行代码更易读。这篇文章介绍了它们的工作原理,这些信息可能在其他文章中没有重复。

将以下内容添加到你的~/.bashrc文件中:

export PROMPT_COMMAND='hpwd=$(history 1); hpwd="${hpwd# *[0-9]*  }"; if [[ ${hpwd%% *} == "cd" ]]; then cwd=$OLDPWD; else cwd=$PWD; fi; hpwd="${hpwd% ### *} ### $cwd"; history -s "$hpwd"'

这会生成一个类似于下面这样的历史记录条目:
rm subdir/file ### /some/dir

我使用###作为注释定界符,以使其与用户可能输入的注释区别开来,并减少在空白命令行上按下回车时会累积的旧路径注释被剥离的机会。不幸的是,这样做的副作用是一个命令像echo " ### "就会变形,尽管这应该相当罕见。
有些人可能会觉得我重复使用同一变量名很不舒服。通常情况下我不会这样做,但在这里我试图将其最小化。无论如何,这很容易更改。
它盲目地假设您没有使用HISTTIMEFORMAT或以其他方式修改历史记录。可以在注释中添加date命令代替HISTTIMEFORMAT功能。然而,如果由于某种原因您需要使用它,它仍然可以在子shell中正常工作,因为它会自动取消设置:
$ htf="%Y-%m-%d %R "    # save it for re-use
$ (HISTTIMEFORMAT=$htf; history 20)|grep 11:25

这里有一些非常小的问题。其中之一是如果您像这样使用history命令:

$ history 3
echo "hello world" ### /home/dennis
ls -l /tmp/file ### /home/dennis
history 3

结果不会在 history 命令本身上显示注释,尽管如果按上箭头或发出另一个 history 命令,您会看到它。
另一个问题是带有嵌入式换行符的命令会在历史记录中留下未注释的副本,除了已注释的副本。
可能会出现其他问题。 如果您发现任何问题,请告诉我。
工作原理
每次发出 PS1 主提示符时,Bash 执行包含在 PROMPT_COMMAND 变量中的命令。 这个小脚本利用这一点来获取历史记录中的最后一个命令,添加注释并保存回去。
以下是拆分开的脚本,其中包含注释:
hpwd=$(history 1)              # grab the most recent command
hpwd="${hpwd# *[0-9]*  }"      # strip off the history line number
if [[ ${hpwd%% *} == "cd" ]]   # if it's a cd command, we want the old directory
then                           #   so the comment matches other commands "where *were* you when this was done?"
    cwd=$OLDPWD
else
    cwd=$PWD
fi
hpwd="${hpwd% ### *} ### $cwd" # strip off the old ### comment if there was one so they 
                               #   don't accumulate, then build the comment
history -s "$hpwd"             # replace the most recent command with itself plus the comment

1
嗨,Dennis,我写了一个使用这段代码的Bash脚本,想让你感兴趣:https://github.com/pconerly/lc-listcommands-bash 我很想听听你的反馈。 - Civilian
1
我在history的结果中得到了两行,一行没有注释,另一行有注释。 - Gauthier
@Gauthier,我曾经遇到过同样的问题,并通过在history -s之前包含一个history -d $(hpwd% *)来解决它。类似这样:PROMPT_COMMAND='hpwd=$(history 1); history -d ${hcmnt% *}; hpwd="${hpwd# *[0-9]* }"; if [[ ${hpwd%% *} == "cd" ]]; then cwd=$OLDPWD; else cwd=$PWD; fi; hpwd="${hpwd% ### *} ### $cwd"; history -s "$hpwd"' - humanityANDpeace
@DennisWilliamson 这是真正的宝藏!非常感谢你。 - Stefan Ludwig
@DennisWilliamson 非常喜欢这个功能。是否可以在实际命令之前将目录输出作为自己的“伪”命令添加到历史数据库中?当前的解决方案非常好,但是当使用向上、向下、!和其他方式浏览历史记录时,它会强制我回退到“### cwd”并将其删除,然后才能从历史记录中运行命令。是的,使用“###”运行命令没有影响(这就是你建议的原因!),但是当需要修改以前的命令时,需要导航到“###”之前或者回退到它并将其删除。 - ePhrygian
显示剩余10条评论

21

hcmnt - 长函数版

这是一个长函数版本。虽然它有些臃肿,但它添加了几个有用的功能。我也发布了一个一行代码的版本和一个更短的函数版本。我喜欢函数版本,因为它们不会覆盖您环境中的其他变量,并且比一行代码更易读。请阅读下面的函数入口和注释以获取有关其工作方式和一些限制的其他信息。为了保持组织的整洁性,我在每个版本中都发布了一个答案。

要使用此函数,请将其保存在名为hcmnt的文件中,位于像/usr/local/bin这样的位置(如果需要,可以使用chmod +x命令)。然后在您的~/.bashrc中按如下方式调用:

source /usr/local/bin/hcmnt
export hcmntextra='date "+%Y%m%d %R"'
export PROMPT_COMMAND='hcmnt'

不要编辑设置了 PROMPT_COMMANDhcmntextra 的函数文件。将它们保留为默认值并在你的 .bashrc 中按上述方法包含它们,并在那里编辑以设置 hcmnt 的选项或更改或取消设置 hcmntextra。与短函数不同,使用此函数必须同时设置 hcmntextra 变量并使用 -e 选项才能使该功能生效。
您可以添加几个选项,这些选项在函数的注释中有记录(附有一些示例)。一个值得注意的特性是将带有附加注释的历史记录条目记录到文件中,而保留实际历史记录不变。为了使用此函数,只需添加 -l filename 选项,如下所示:
export PROMPT_COMMAND="hcmnt -l ~/histlog"

您可以使用任何选项的组合,但-n-t是互斥的。

#!/bin/bash
hcmnt() {

# adds comments to bash history entries (or logs them)

# by Dennis Williamson - 2009-06-05 - updated 2009-06-19
# https://dev59.com/_HNA5IYBdhLWcg3wfNyO
# (thanks to Lajos Nagy for the idea)

# the comments can include the directory
# that was current when the command was issued
# plus optionally, the date or other information

# set the bash variable PROMPT_COMMAND to the name
# of this function and include these options:

    # -e - add the output of an extra command contained in the hcmntextra variable
    # -i - add ip address of terminal that you are logged in *from*
    #      if you're using screen, the screen number is shown
    #      if you're directly logged in, the tty number or X display number is shown
    # -l - log the entry rather than replacing it in the history
    # -n - don't add the directory
    # -t - add the from and to directories for cd commands
    # -y - add the terminal device (tty)
    # text or a variable

# Example result for PROMPT_COMMAND='hcmnt -et $LOGNAME'
#     when hcmntextra='date "+%Y%m%d %R"'
# cd /usr/bin ### mike 20090605 14:34 /home/mike -> /usr/bin

# Example for PROMPT_COMMAND='hcmnt'
# cd /usr/bin ### /home/mike

# Example for detailed logging:
#     when hcmntextra='date "+%Y%m%d %R"'
#     and PROMPT_COMMAND='hcmnt -eityl ~/.hcmnt.log $LOGNAME@$HOSTNAME'
#     $ tail -1 ~/.hcmnt.log
#     cd /var/log ### dave@hammerhead /dev/pts/3 192.168.1.1 20090617 16:12 /etc -> /var/log


# INSTALLATION: source this file in your .bashrc

    # will not work if HISTTIMEFORMAT is used - use hcmntextra instead
    export HISTTIMEFORMAT=

    # HISTTIMEFORMAT still works in a subshell, however, since it gets unset automatically:

    #   $ htf="%Y-%m-%d %R "    # save it for re-use
    #   $ (HISTTIMEFORMAT=$htf; history 20)|grep 11:25

    local script=$FUNCNAME

    local hcmnt=
    local cwd=
    local extra=
    local text=
    local logfile=

    local options=":eil:nty"
    local option=
    OPTIND=1
    local usage="Usage: $script [-e] [-i] [-l logfile] [-n|-t] [-y] [text]"

    local newline=$'\n' # used in workaround for bash history newline bug
    local histline=     # used in workaround for bash history newline bug

    local ExtraOpt=
    local LogOpt=
    local NoneOpt=
    local ToOpt=
    local tty=
    local ip=

    # *** process options to set flags ***

    while getopts $options option
    do
        case $option in
            e ) ExtraOpt=1;;        # include hcmntextra
            i ) ip="$(who --ips -m)" # include the terminal's ip address
                ip=($ip)
                ip="${ip[4]}"
                if [[ -z $ip ]]
                then
                    ip=$(tty)
                fi;;
            l ) LogOpt=1            # log the entry
                logfile=$OPTARG;;
            n ) if [[ $ToOpt ]]
                then
                    echo "$script: can't include both -n and -t."
                    echo $usage
                    return 1
                else
                    NoneOpt=1       # don't include path
                fi;;
            t ) if [[ $NoneOpt ]]
                then
                    echo "$script: can't include both -n and -t."
                    echo $usage
                    return 1
                else
                    ToOpt=1         # cd shows "from -> to"
                fi;;
            y ) tty=$(tty);;
            : ) echo "$script: missing filename: -$OPTARG."
                echo $usage
                return 1;;
            * ) echo "$script: invalid option: -$OPTARG."
                echo $usage
                return 1;;
        esac
    done

    text=($@)                       # arguments after the options are saved to add to the comment
    text="${text[*]:$OPTIND - 1:${#text[*]}}"

    # *** process the history entry ***

    hcmnt=$(history 1)              # grab the most recent command

    # save history line number for workaround for bash history newline bug
    histline="${hcmnt%  *}"

    hcmnt="${hcmnt# *[0-9]*  }"     # strip off the history line number

    if [[ -z $NoneOpt ]]            # are we adding the directory?
    then
        if [[ ${hcmnt%% *} == "cd" ]]    # if it's a cd command, we want the old directory
        then                             #   so the comment matches other commands "where *were* you when this was done?"
            if [[ $ToOpt ]]
            then
                cwd="$OLDPWD -> $PWD"    # show "from -> to" for cd
            else
                cwd=$OLDPWD              # just show "from"
            fi
        else
            cwd=$PWD                     # it's not a cd, so just show where we are
        fi
    fi

    if [[ $ExtraOpt && $hcmntextra ]]    # do we want a little something extra?
    then
        extra=$(eval "$hcmntextra")
    fi

    # strip off the old ### comment if there was one so they don't accumulate
    # then build the string (if text or extra aren't empty, add them plus a space)
    hcmnt="${hcmnt% ### *} ### ${text:+$text }${tty:+$tty }${ip:+$ip }${extra:+$extra }$cwd"

    if [[ $LogOpt ]]
    then
        # save the entry in a logfile
        echo "$hcmnt" >> $logfile || echo "$script: file error." ; return 1
    else

        # workaround for bash history newline bug
        if [[ $hcmnt != ${hcmnt/$newline/} ]] # if there a newline in the command
        then
            history -d $histline # then delete the current command so it's not duplicated
        fi

        # replace the history entry
        history -s "$hcmnt"
    fi

} # END FUNCTION hcmnt

# set a default (must use -e option to include it)
export hcmntextra='date "+%Y%m%d %R"'      # you must be really careful to get the quoting right

# start using it
export PROMPT_COMMAND='hcmnt'

更新于2009-06-19:添加了有助于记录日志的选项(IP和TTY),解决了重复条目问题的解决方法,删除了多余的空赋值。


“HISTTIMEFORMAT仍然在子shell中起作用,但是由于子shell退出时自动取消设置,因此要注意。” - Dennis Williamson
@Janus:我觉得这个测试是不必要的。你可以在没有设置的情况下取消设置一个变量而不会出错。 - Dennis Williamson
由于某些版本中,“--ips” 似乎已成为默认选项。对于这个含糊的表达,我感到抱歉。我无法在 coreutil 源代码修订中找到此选项的任何信息。如果有人知道,请告诉我。 - simonmysun
“--ips” 存在于 Debian 补丁中。 - simonmysun
我建议使用trap 'hcmnt' DEBUG而不是PROMPT_COMMAND='hcmnt',可以避免在 shell 被终止时出现历史记录丢失的情况。 - simonmysun
显示剩余3条评论

16
你可以安装 Advanced Shell History,这是一个开源工具,它会将你在 bashzsh 中使用的历史记录写入 sqlite 数据库。记录内容包括当前工作目录、命令退出码、命令开始和结束时间、会话开始和结束时间、tty 等等。
如果你想查询历史记录数据库,可以编写自己的 SQL 查询语句,并在捆绑的 ash_query 工具中保存并提供它们。这里有一些有用的预设查询,但由于我很熟悉 SQL,所以通常只是打开数据库并交互式地查询需要查找的内容。
不过,我发现其中一个查询非常有用,那就是查看当前工作目录的历史记录。这有助于我记住在处理某些事情时离开的位置。
vagrant@precise32:~$ ash_query -q CWD
session
    when                   what
1
    2014-08-27 17:13:07    ls -la
    2014-08-27 17:13:09    cd .ash
    2014-08-27 17:16:27    ls
    2014-08-27 17:16:33    rm -rf advanced-shell-history/
    2014-08-27 17:16:35    ls
    2014-08-27 17:16:37    less postinstall.sh
    2014-08-27 17:16:57    sudo reboot -n

使用当前工作目录及其子目录的相同历史记录:

vagrant@precise32:~$ ash_query -q RCWD
session
    where
        when                   what
1
    /home/vagrant/advanced-shell-history
        2014-08-27 17:11:34    nano ~/.bashrc
        2014-08-27 17:12:54    source /usr/lib/advanced_shell_history/bash
        2014-08-27 17:12:57    source /usr/lib/advanced_shell_history/bash
        2014-08-27 17:13:05    cd
    /home/vagrant
        2014-08-27 17:13:07    ls -la
        2014-08-27 17:13:09    cd .ash
    /home/vagrant/.ash
        2014-08-27 17:13:10    ls
        2014-08-27 17:13:11    ls -l
        2014-08-27 17:13:16    sqlite3 history.db
        2014-08-27 17:13:43    ash_query
        2014-08-27 17:13:50    ash_query -Q
        2014-08-27 17:13:56    ash_query -q DEMO
        2014-08-27 17:14:39    ash_query -q ME
        2014-08-27 17:16:26    cd
    /home/vagrant
        2014-08-27 17:16:27    ls
        2014-08-27 17:16:33    rm -rf advanced-shell-history/
        2014-08-27 17:16:35    ls
        2014-08-27 17:16:37    less postinstall.sh
        2014-08-27 17:16:57    sudo reboot -n

FWIW(For What It's Worth)- 我是该项目的作者和维护者。


7

hcmnts - 短函数版本

这是一个以函数形式呈现的短版本。我还发布了一个一行代码的原版和一个带有多个附加功能的长函数。我喜欢函数版本,因为它们不会覆盖您环境中的其他变量,并且比一行代码更易读。请阅读一行代码的条目以获取有关如何使用以及一些限制的其他信息。为了保持组织更加有序,我已经将每个版本都发布在自己的答案中。

要使用此版本,请将其保存到名为 hcmnts 的文件中,放在类似 /usr/local/bin 的位置(如果需要,可以使用 chmod + x 进行授权),然后在您的 ~/.bashrc 中像这样引用:

source /usr/local/bin/hcmnts

如果您不想要日期和时间,可以将设置hcmntextra的行注释掉(或更改其格式或使用其他命令而非date)。

就是这样简单。

#!/bin/bash
hcmnts() {
    # adds comments to bash history entries

    # the *S*hort version of hcmnt (which has many more features)

    # by Dennis Williamson
    # https://dev59.com/_HNA5IYBdhLWcg3wfNyO
    # (thanks to Lajos Nagy for the idea)

    # INSTALLATION: source this file in your .bashrc

    # will not work if HISTTIMEFORMAT is used - use hcmntextra instead
    export HISTTIMEFORMAT=

    # HISTTIMEFORMAT still works in a subshell, however, since it gets unset automatically:

    #   $ htf="%Y-%m-%d %R "    # save it for re-use
    #   $ (HISTTIMEFORMAT=$htf; history 20)|grep 11:25

    local hcmnt
    local cwd
    local extra

    hcmnt=$(history 1)
    hcmnt="${hcmnt# *[0-9]*  }"

    if [[ ${hcmnt%% *} == "cd" ]]
    then
        cwd=$OLDPWD
    else
        cwd=$PWD
    fi

    extra=$(eval "$hcmntextra")

    hcmnt="${hcmnt% ### *}"
    hcmnt="$hcmnt ### ${extra:+$extra }$cwd"

    history -s "$hcmnt"
}
export hcmntextra='date +"%Y%m%d %R"'
export PROMPT_COMMAND='hcmnts'

2
对于那些想在 zsh 中使用此功能的人,我已经 修改 了 Jeet Sukumaran 的实现和 percol,以便进行交互式关键字搜索和提取命令或执行路径。还可以过滤掉重复的命令并隐藏字段(日期、命令、路径)。

2

以下是我使用的一行代码。将其放在这里是因为它要简单得多,我对每个会话的历史记录没有任何问题,我只是希望有一个与工作目录相关的历史记录。

另外,上面那个一行代码也会对您的用户界面产生影响太大。

export PROMPT_COMMAND='if [ "$(id -u)" -ne 0 ]; then echo "$(date "+%Y-%m-%d.%H:%M:%S") $(pwd) $(history 1)" >> ~/.bash.log; fi'

由于我的主目录通常是一个跨挂载的 Gluster 文件系统,这会导致它成为我所做的一切操作的历史记录。根据您的工作环境,可以选择将 $(hostname) 添加到上面的 echo 命令中...

即使有 10 万个条目,grep 也足够好用了。无需使用 sqlite 记录日志。只需不要在命令行上输入密码即可。密码已经过时了!

此外,对于搜索,我倾向于执行以下操作:

function hh() {
    grep "$1" ~/.bash.log
}

好的,你需要添加“-a”参数将它追加到历史记录文件中。 - stack0114106

1
先生们,这个更好用。唯一我无法弄清楚的是如何使脚本在登录时不记录到系统日志中,并记录历史记录中的最后一个命令。但到目前为止,它的效果非常好。
#!/bin/bash
trackerbash() { # 将注释添加到 Bash 历史记录条目中
# 由 Dennis Williamson 修改 # https://dev59.com/_HNA5IYBdhLWcg3wfNyO # (感谢 Lajos Nagy 的建议)
# Supper Enhanced by QXT
# 安装:在您的 .bashrc 文件中引用此文件
export HISTTIMEFORMAT= # export HISTTIMEFORMAT='%F %T '
local hcmnt local cwd local extra local thistty local whoiam local sudouser local shelldate local TRACKIP local TRACKHOST
thistty=`/usr/bin/tty|/bin/cut -f3-4 -d/` whoiam=`/usr/bin/whoami` sudouser=`last |grep $thistty |head -1 | awk '{ print $1 }' |cut -c 1-10` hcmnt=$(history 1) hcmnt="${hcmnt# *[0-9]* }" cwd=`pwd`
hcmnt="${hcmnt% ### *}" hcmnt=" $hcmnt ${extra:+$extra }"
shelldate=`date +"%Y %b %d %R:%S"` TRACKHOST=`whoami | sed -r "s/.*\((.*)\).*/\\1/"` TRACKIP=`last |grep $thistty |head -1 | awk '{ print $3 }'`
logger -p local1.notice -t bashtracker -i -- "$sudouser ${USER}: $thistty: $TRACKIP: $shelldate: $cwd : $hcmnt" history -w
} export PROMPT_COMMAND='trackerbash'

嗨,之前那个对我有效,但这一个似乎不起作用。 它对每个人都有效吗? - Rgonomike

1
充分披露:我是FOSS工具 shournal - A (file-) journal for your shell 的作者:
使用它的 bash集成,命令的工作目录也存储在shournal的sqlite数据库中,可以通过以下方式检索:
shournal --query -cmdcwd "$PWD"

查询子工作目录可以使用以下命令:
shournal --query -cmdcwd -like "$PWD/%"

0
你可以考虑一个独立的项目(我写的),支持为每个命令保存路径:https://github.com/chrissound/MoscoviumOrange 在这里,你可以添加一个钩子到Bash中,以保存每个条目:
$(jq -n --arg command "$1" --arg path "$PWD" '{"command":$command, "path":$path}' | "$(echo 'readlink -f $(which nc)' | nix run nixpkgs.netcat)" -N -U ~/.config/moscoviumOrange/monitor.soc &)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接