Command validations with ZSH
Tags: productivity, zsh, tutorial, terminal
Published: 2021-03-08 18:32:24

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.

```bash
local quit="y"
local cmd=""
```

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.

```bash
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
}
```

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.

```bash
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
}
```

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.

```bash
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
}
```

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.

```bash
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"
}
```

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

```bash
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
```

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

```bash
# add-zsh-hook -d preexec pre_validation  # Remove it for this hook
# add-zsh-hook -d precmd pos_validation   # Remove it for this hook
```

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

--- 

# Example

![danger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80a9ldz9g3mca6ssghl8.png)

![pre](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptz86jwn6k9d8eiew00j.png)

![pos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chinb6eqz9v0lo17ysjj.png)

---

You can download or clone this code and other ZSH utilities from GitHub: [dot Files](https://github.com/deinsoftware/dot-files) repository.

---

**That’s All Folks!**
**Happy Coding** 🖖

[![beer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkgsx4aw6qn9i9ax6as9.png)](https://github.com/sponsors/deinsoftware)

> Sources
> - [Bash Colors](https://misc.flogisoft.com/bash/tip_colors_and_formatting)