DEV Community

Cover image for Command validations with ZSH
Camilo Martinez
Camilo Martinez

Posted on • Edited on

Command validations with ZSH

I'm tired of forgetting to run some commands before and others after a process. So I decided to give this responsibility to the terminal. Why not?

In ZSH we have two hooks preexec (after) and precmd (before) that can be used to achieve this task.

Validations

We need two variables, one to know if the process was quite early to not run the precmd (before) hook and another to know which command is running actually.

Open ~/.zshrc file and add these variables to the script.

local quit="y"
local cmd=""
Enter fullscreen mode Exit fullscreen mode

I always forgot to run npm run build command before npm publish. So we are going to create a function before_validation to show a message to confirm if we had run the command before.

cancel(){
  echo "\e[43m\e[30mALERT:\e[0m Did you run \e[1m'$1'\e[0m command before?"
  echo "\e[32m[ANY] = Continue \e[0m| \e[31m[Ctrl+c] = Cancel \e[0m"
  read -sk key
  quit="n"
}

before_validation(){
  local cmd_validation=""
  local cmd_previous=$(fc -ln -1 | xargs) # Get previous command from history
  local cmd_current=$(echo $1 | xargs)

  if [[ "$cmd_current" =~ ^"npm publish" ]]; then
    cmd_validation="npm run build"
  elif [[ "$cmd_current" =~ ^"git checkout" ]]; then
    cmd_validation="git pull"
  # ---
  # Add all elif (else if) that you need
  elif [[ "$cmd_current" =~ ^"your command" ]]; then
    cmd_validation="command to run before"
  # ---
  fi

  if [[ ! -z $cmd_validation ]]; then
    if [[ "${cmd_validation}" != "${cmd_previous}" ]]; then
      cancel $cmd_validation # show cancel alert if is not the previous
    fi
  else
    quit="n"
  fi
}
Enter fullscreen mode Exit fullscreen mode

Currently, there is no warning about dangerous commands. So we are going to create a function danger_validation to display a warning message and give us a second chance.

danger(){
  echo "\e[41m\e[97mDANGER:\e[0m Are you sure? really?"
  echo "\e[32m[ANY] = Continue \e[0m| \e[31m[Ctrl+c] = Cancel \e[0m"
  read -sk key
  quit="n"
}

danger_validation(){
  local cmd_current=$(echo $1 | xargs)

  if [[ "$cmd_current" =~ ^"rm -rf /" ]]; then
    danger
  elif [[ "$cmd_current" =~ ^"git reset --hard HEAD" ]]; then
    danger
  elif [[ "$cmd_current" =~ ^"git clean -f -d -x" ]]; then
    danger
  # Add all elif (else if) that you need
  elif [[ "$cmd_current" =~ ^"your command" ]]; then
    danger
  # ---
  fi
}
Enter fullscreen mode Exit fullscreen mode

And I also forgot to run npm i command after git pull origin .... So we are going to create a function after_validation to show a message to confirm if we want to run a command after.

run(){
  echo "\e[43m\e[30mALERT:\e[0m Do you want to run \e[1m'$1'\e[0m command after?"
  echo "\e[32m[Y] = Yes \e[0m| \e[31m[ANY] = Cancel \e[0m"
  read -sk key
  if [[ "$key" == "y" ]] || [[ "$key" == "Y" ]]; then
    echo "\e[32m❯ \e[33mRunning...\e[0m"
    eval $1
  fi
}

after_validation(){
  if [[ "$cmd" =~ ^"git pull origin" ]]; then
    run "npm i"
  elif [[ "$cmd" =~ ^"git checkout" ]]; then
    run "npm i"
  elif [[ "$cmd" =~ ^"npm run build" ]]; then
    run "obp" #open build folder alias
  # ---
  # Add all elif (else if) that you need
  elif [[ "$1" =~ ^"your command" ]]; then
    cancel "command to run after"
  # ---
  fi
}
Enter fullscreen mode Exit fullscreen mode

Now we need to create, something like a "factory function" that can be associated with the hooks. I preferred this way because we can call more than one function before or after.

pre_validation() {
  quit="y" && cmd=""
  [[ $# -eq 0 ]] && return # If there's no input, return. Else...
  cmd="$1" # Save global for after validation
  expand_command_line "$@"
  danger_validation "$@"
  before_validation "$@"
}

pos_validation() {
  [[ -z $cmd ]] && return # If there's no cmd, return. Else...
  if [[ "$quit" == "n" ]]; then
    after_validation
  fi
  quit="y"
}
Enter fullscreen mode Exit fullscreen mode

And here, we are going to associate pre_validation function with preexec (before) hook and pos_validation with precmd (after) hook.

autoload -U add-zsh-hook                  # Load the zsh hook module
add-zsh-hook preexec pre_validation       # Adds the pre hook
add-zsh-hook precmd pos_validation        # Adds the pos hook
Enter fullscreen mode Exit fullscreen mode

I'm not planning to remove these hooks, but you can do it with these commands:

# add-zsh-hook -d preexec pre_validation  # Remove it for this hook
# add-zsh-hook -d precmd pos_validation   # Remove it for this hook
Enter fullscreen mode Exit fullscreen mode

Once finish, reopen all terminals or update his source running source ~/.zshrc command and now you are ready to use validations.


Example

danger

pre

pos


You can download or clone this code and other ZSH utilities from GitHub: dot Files repository.


That’s All Folks!
Happy Coding 🖖

beer

Sources

Top comments (1)

Collapse
 
nahoj profile image
Johan Grande • Edited

Nice! This inspired me to put the following in my .zshrc, for all the times I want to know the contents of a variable or what the result of an expansion would be, and I find it tedious to type echo or I outright forget. Now I don't have to anymore. Similar to the auto_cd option.

autoload -U add-zsh-hook
auto_print(){
  if [[ $1 = '$'* ]]; then
    eval "print -r -- $3"
    read -sk key
    print -r "[ANY] = Execute as a command | [Ctrl+c] = Cancel"
    read -sk key
  fi
}
add-zsh-hook preexec auto_print
Enter fullscreen mode Exit fullscreen mode

Which gives:

% x=42
% $x  # Enter + Ctrl-c
42
% $x  # Enter + Enter + Ctrl-c
42
[ANY] = Execute as a command | [Ctrl+c] = Cancel
% $x  # Enter + Enter + Enter
42
[ANY] = Execute as a command | [Ctrl+c] = Cancel
zsh: command not found: 42
% $((10 + 2 ** 5))
42
Enter fullscreen mode Exit fullscreen mode