2015-05-15 8 views
3

Недавно я хочу попробовать Z-оболочку в Mac. Но я хотел бы продолжить также сохранение истории команд в ~/.persistent_history, что я и сделал в Bash (ref).Сохраните историю Zsh до ~/.persistent_history

Однако сценарий в ссылке реф не работает под Zsh:

log_bash_persistent_history() 
{ 
    [[ 
    $(history 1) =~ ^\ *[0-9]+\ +([^\ ]+\ [^\ ]+)\ +(.*)$ 
    ]] 
    local date_part="${BASH_REMATCH[1]}" 
    local command_part="${BASH_REMATCH[2]}" 
    if [ "$command_part" != "$PERSISTENT_HISTORY_LAST" ] 
    then 
    echo $date_part "|" "$command_part" >> ~/.persistent_history 
    export PERSISTENT_HISTORY_LAST="$command_part" 
    fi 
} 
run_on_prompt_command() 
{ 
    log_bash_persistent_history 
} 
PROMPT_COMMAND="run_on_prompt_command" 

Есть ли кто-нибудь, кто может помочь мне получить его работу? Большое спасибо!

+1

[Это] (http://superuser.com/questions/735660/whats-the-zsh-equivalent-of-bashs-prompt-command) должно помочь с заменой 'PROMPT_COMMAND'. Замена '[[' использование должно выполняться с помощью 'grep -o' или' cut' или аналогичного, но зависит от точного вывода 'history' в zsh. –

+0

@EtanReisner Большое спасибо! Для «PROMPT_COMMAND» ссылка должна быть полезна.Для '[[' part, я только что нашел с помощью команды 'history', bash даст последнюю строку (в данном случае это' history') в последней строке. Но в Zsh команда 'history' не будет возвращать самую новую, она возвращает команду, используемую до' history' в последней строке. Есть идеи? :-) – astroboylrx

+0

Ну, я не вижу причин изобретать колесо. Просто установите 'HISTFILE' и установите' HISTSIZE' и 'SAVEHIST' в некоторые смехотворно большие размеры (мои 100 000, и я не вижу причин сделать их более крупными, так как я регистрирую все свои сеансы терминала в iTerm2 - это все команды + вывод , со временем до нескольких секунд в моей подсказке). Формат истории по умолчанию имеет связанные с POSIX временные метки, которые более точны, чем ваши, поскольку у вас нет tzinfo. – 4ae1e1

ответ

2

После стольких поисковых запросов, я наконец выяснил, как это сделать. Во-первых, в ~/.zshrc, добавьте следующие параметры для манипуляции историей:

setopt append_history # append rather then overwrite 
setopt extended_history # save timestamp 
setopt inc_append_history # add history immediately after typing a command 

Короче говоря, эти три варианта будут записывать каждый input_time + команду ~/.zsh_history немедленно. Затем поместите эту функцию в ~/.zshrc:

precmd() { # This is a function that will be executed before every prompt 
    local date_part="$(tail -1 ~/.zsh_history | cut -c 3-12)" 
    local fmt_date="$(date -d @${date_part} +'%Y-%m-%d %H:%M:%S')" 
    # For older version of command "date", comment the last line and uncomment the next line 
    #local fmt_date="$(date -j -f '%s' ${date_part} +'%Y-%m-%d %H:%M:%S')" 
    local command_part="$(tail -1 ~/.zsh_history | cut -c 16-)" 
    if [ "$command_part" != "$PERSISTENT_HISTORY_LAST" ] 
    then 
     echo "${fmt_date} | ${command_part}" >> ~/.persistent_history 
     export PERSISTENT_HISTORY_LAST="$command_part" 
    fi 
} 

Поскольку я использую как Баш и ЗШ, поэтому я хочу, файл, который может сохранить все свои команды истории. В этом случае я могу легко найти их все, используя grep.

+0

Это не работает с несколькими командами. Следующие работы: 'local date_part =" $ (gawk 'substr ($ 0, 0, 1) == ":" {print;}' ~/.zsh_history | tail -1 | cut -c 3-12) "' –

1

оригинальный ответ в основном хорошо, но для обработки команд многострочные, которые также содержат символ «:», например, это работает:

local line_num_last=$(grep -ane '^:' ~/.zsh_history | tail -1 | cut -d':' -f1 | tr -d '\n') 
local date_part="$(gawk "NR == $line_num_last {print;}" ~/.zsh_history | cut -c 3-12)" 
local fmt_date="$(date -d @${date_part} +'%Y-%m-%d %H:%M:%S')" 
local command_part="$(gawk "NR >= $line_num_last {print;}" ~/.zsh_history | sed -re '1s/.{15}//')" 
+0

Круто! Я не думал о многострочных командах. Это очень полезно. Спасибо огромное! :-) – astroboylrx

+0

Я добавляю еще одну строку, чтобы многострочные команды сохранялись в виде однострочных команд: 'local one_line_command = $ {(Q) command_part // \\ $ '\ n' /}'. Теперь это выглядит великолепно. :-) – astroboylrx

0

Не могу пока комментировать (и это выходит за рамки простой коррекция), поэтому я добавлю это как ответ.

This correction к the accepted answer не совсем работает, когда, например, последняя команда взяла совсем немного времени, чтобы выполнить - вы получите паразитных номера и ; в вашей команде, например:

2017-07-22 19:02:42 | 3;micro ~/.zshrc && . ~/.zshrc 

Это может быть исправлено путем замены sed -re '1s/.{15}//' в command_part с немного больше gawk, который также избегает нас трубопровода:

local command_part="$(gawk " 
    NR == $line_num_last { 
    pivot = match(\$0, \";\"); 
    print substr(\$0, pivot+1); 
    } 
    NR > $line_num_last { 
    print; 
    }" ~/.zsh_history)" 

Он также имеет проблемы при deali ng с многострочными командами, где одна из строк начинается с :. Это может быть (в основном) зафиксировано заменой grep -ane '^:' ~/.zsh_history на line_num_last на grep -anE '^: [0-9]{10}:[0-9]*?;' ~/.zsh_history. Я говорю в основном потому, что команда может содержать строку, соответствующую этому выражению. Скажем,

% naughty "multiline 
> command 
> ::123;but a command I'm not 
> " 

Который приведет к затерта записи в ~/.persistent_history.

Чтобы исправить это, мы должны, в свою очередь, проверить, заканчивается ли предыдущий redord с \ (там могут быть и другие условия, но я не знаком с этим форматом истории), и если да попробовать предыдущий матч ,

_get_line_num_last() { 
    local attempts=0 
    local line=0 
    while true; do 
    # Greps the last two lines that can be considered history records 
    local lines="$(grep -anE '^: [0-9]{10}:[0-9]*?;' ~/.zsh_history | \ 
       tail -n $((2 + attempts)) | head -2)" 
    local previous_line="$(echo "$lines" | head -1)" 
    # Gets the line number of the line being tested 
    local line_attempt=$(echo "$lines" | tail -1 | cut -d':' -f1 | tr -d '\n') 
    # If the previous (possible) history records ends with `\`, then the 
    # _current_ one is part of a multiline command; try again. 
    # Probably. Unless it was in turn in the middle of a multi-line 
    # command. And that's why the last line should be saved. 
    if [[ $line_attempt -ne $HISTORY_LAST_LINE ]] && \ 
     [[ $previous_line == *"\\" ]] && [[ $attempts -eq 0 ]]; 
    then 
     ((attempts+=1)) 
    else 
     line=$line_attempt 
     break 
    fi 
    done 
    echo "$line" 
} 
precmd() { 
    local line_num_last="$(_get_line_num_last)" 
    local date_part="$(gawk "NR == $line_num_last {print;}" ~/.zsh_history | cut -c 3-12)" 
    local fmt_date="$(date -d @${date_part} +'%Y-%m-%d %H:%M:%S')" 
    # I use awk itself to split the _first_ line only at the first `;` 
    local command_part="$(gawk " 
    NR == $line_num_last { 
     pivot = match(\$0, \";\"); 
     print substr(\$0, pivot+1); 
    } 
    NR > $line_num_last { 
     print; 
    }" ~/.zsh_history)" 
    if [ "$command_part" != "$PERSISTENT_HISTORY_LAST" ] 
    then 
    echo "${fmt_date} | ${command_part}" >> ~/.persistent_history 
    export PERSISTENT_HISTORY_LAST="$command_part" 
    export HISTORY_LAST_LINE=$((1 + $(wc -l < ~/.zsh_history))) 
    fi 
} 
Смежные вопросы