Skip to content
/ bash.sh Public

main entry template of your first bash script file

License

Notifications You must be signed in to change notification settings

hedzr/bash.sh

Repository files navigation

bash.sh

bash.sh is a starter template for shell developing.

image-20240817103856118

File bash.config can be applied and sourced into your zshell environment directly. See Import bash.config into your zsh env. This will allow loading any tools lazily and automatically from $HOME/.local/bin/ops.d/{lazy,apt,yum,zypper,ubuntu,opensuse-leap,...}.

Features

We devote ourselves to helping you to write shell script functions more easier.

  • Write lots of functions and make calling them easier
  • Write many functions and organize them hierarchically
  • Work for multiple devops environments: making ops command and its subcommands; decorating your local zshell init scripts for managing the useful utilities.

History

  • v20241021
    • improved is_git_dirty
    • added if_systemd, if_sysv (init), if_upstart
  • CHANGELOG

Usages

Command Line Options

# internal commands
$ ./bash.sh cool
$ ./bash.sh sleep

# see debug-info (environment checks)
$ DEBUG=1 ./bash.sh
$ ./bash.sh debug-info

# internal helpers
$ ./bash.sh 'is_root && echo Y'
$ sudo ./bash.sh 'is_root && echo Y'
$ sudo DEBUG=1 ./bash.sh 'is_root && echo y'

# at end of the execution
$ HAS_END=: ./bash.sh
$ HAS_END=false ./bash.sh

Use It

1. Simple

Copy bash.sh and rename as your main entry (such as: mgr), and go ahead.

Modify _my_main_do_sth() as you want.

Example:

wget https://raw.githubusercontent.com/hedzr/bash.sh/master/bash.sh
mv bash.sh installsamba
DEBUG=1 ./installsamba

2. Global

Use installer
curl -sSL https://github.com/hedzr/bash.sh/raw/refs/heads/master/installer | bash -s

installer will copy bash.config to ~/.local/bin/bash.sh.

installer makes a symbolic link ops to bash.config, you can run the builtin functions with it: try ops debug-info to ensure it works.

Manually

Copy bash.config to /usr/local/bin/ or anywhere you prefer, and source it from your script file.

Some examples here.

3. Import bash.config into your zsh env

You may import bash.config into your local zshell environment. This brings a extensible structure to you: the bash.sh loader (in darwin-only.sh) will source in the scripts at these places (macOS only):

  1. /path/to/bash.sh/ops.d/*.sh
  2. /path/to/bash.sh/ops.d/{darwin,windows,ubuntu,...}/*.sh
  3. /path/to/bash.sh/ops.d/{brew,apt,dnf,yum,...}*.sh
  4. $HOME/.local/bin/.zsh/*.sh

Since we shipped bash.sh with ./ops.d/darwin/*.sh, so the features above will be available. As an extra feature, Lazy loading machanism is available too. Just put your .sh tool into this folder:

  1. $HOME/.local/bin/.zsh/lazy/
  2. $HOME/.local/bin/ops.d/lazy/*.sh

It will be loaded and invoked on-demand.

As a sample, you could save vm.sh into ~/.local/bin/.zsh, and save vagrant_run.sh into ~/.local/bin/.zsh/lazy, and run it:

vm ls
vm run u20s.local
vm sizes

function vm (in vm.sh) will be invoked directly, and function vagrant_run will be lazy-loaded and invoked implicitly.

Do same action on vmware_run.sh.

We have serveral posts in chinese to introduce vm() (HERE).

How?

Put these codes in your $HOME/.zshenv(or $HOME/.bashrc):

### BASH.SH/.CONFIG ####################################
{
  f="/path/to/bash.sh/bash.config"
  [ -f "$f" ] && DEBUG=1 VERBOSE=0 source "$f" >>/tmp/sourced.list
  unset cool sleeping _my_main_do_sth main_do_sth dir f DEBUG VERBOSE currentShell
}
### BASH.SH/.CONFIG END ################################

It can be simplified to one-liner:

. "/path/to/bash.sh/bash.config" && unset cool sleeping _my_main_do_sth main_do_sth DEBUG VERBOSE currentShell

Write functions and call them

First of bash.sh is, we collected and organized many small functions and utilities to build a basic framework so you can pass the writing about entry point and arguments parsing, and so on.

Which means, you're working for checking how many nodes are online, you may write count_nodes() function in a blank script file count-nodes.sh, and append the fragment between #### HZ Tail BEGIN #### and #### HZ Tail END #### from file bash.sh.

count_nodes() {
  local count="$#"
  echo "nodes count is $count, they are: $@"
}

Now you have a highly extensible script, it'll be used as:

# call your count_nodes()
$ ./count-nodes.sh count_nodes
# call it with arguments
$ ./count-nodes.sh count_nodes rabbit.ops.local

Any arguments will be passed into count-nodes.

It's highly extensible. You may add second function enter_node in it:

enter_node() {
  local node="$1"
  echo "entering $node ..., $@"
}

Call it is simple:

$ ./count-nodes.sh enter_nodes redis.ops.local 1 2 3
entering redis.ops.local ..., 1 2 3

Use commander() in your scripts

commander make writing multi-level subcommands simple. It assumes first argument as subcommand and try invoking the responsed function by join the subcommand to current command. So dns add is same of invoking dns_add.

Here is a example codes in an ops:

dns() {
 dns_entry() { commander $(strip_r $(fn_name) _entry) "$@"; }
 dns_usage() {
  cat <<-EOF
   Usage: $0 $self <sub-command> [...]
   Sub-commands:
     ls [--all|-a|--cname|--txt|--one|-1] [string]   list all/most-of-all/generics matched dns-records
     dump                    [RESERVED] dump dns-records [just for dns01]
     nsupdate                [DNS] [DYN] [MODIFY]
     fix_nameservers         [ali] general fix nameservers, step 1
     vpc_fix                 [ali] for VPC envir
     profile                 [ali] make a query perf test and report
     check                   [ali] check dns query is ok [version 1]
     check_2                 [ali] check dns query is ok [version 2]
     check_resolv_conf       [ali] check resolv.conf is right

   Examples:
     $ ops dns ls          # just print the pyhsical ECS' A records
     $ ops dns ls --all
     $ ops dns ls --cname
     $ ops dns ls --txt
     $ ops dns ls sw0
     $ ops dns nsupdate-add sw0ttt00 10.0.24.30
     $ ops dns nsupdate-del sw0ttt00
     $ ops dns nsupdate-add mongo cname mgo.ops.local
     $ ops dns nsupdate-del mongo cname

  EOF
 }

 dns_check() {
  echo "dns check"
 }
 dns_check_2() {
  echo "dns check 2"
 }
 dns_ls() { :; }
 dns_dump() { echo dump dns; }
 dns_nsupdate() { :; }
 dns_ls() { :; }
 dns_vpc_fix() { :; }
 dns_profile() { :; }
 dns_check_resolv_conf() { :; }

 # sub of sub-commands
 #dns_fix()        { dns_entry "$@"; }
 dns_fix_entry() { commander $(strip_r $(fn_name) _entry) "$@"; }
 dns_fix_usage() {
  cat <<-EOF
   Usage: $0 $self <sub-command> [...]
   Sub-commands:
     nameservers             [ali] general fix nameservers, step 1
     resolv_conf             [ali] for VPC envir

   Examples:
     $ ops dns fix nameservers
     $ ops dns fix resolv_conf

  EOF
 }
 dns_fix_nameservers() { echo dns_fix_nameservers; }
 dns_fix_resolv_conf() { echo dns_fix_resolv_conf; }

 dns_entry "$@"
}

and the usage of ops command will be:

ops dns ls
ops dns check
ops dns check_2
ops dns dump

# sub of sub-commands
ops dns fix nameservers
ops dns fix resolv_conf
ops dns fix_nameservers
ops dns fix-nameservers

See also example/dns-tool, Or ./ops.d/darwin/lazy/dns-ops.sh.

Write a lazy function

The best practice of writing a lazy function is, you should create two functions: one is auto-imported, such as a name autocmd, and the two is lazy, named as autocmd-lazy.

The script file autocmd.sh of the first function be put into ~/.local/bin/.zsh/, that's one of auto-loading location. The file autocmd-lazy.sh be put into ~/.local/bin/.zsh/lazy, this is the lazy loading folder for its upper directory.

The script codes are:

# autocmd.sh
autocmd() { autocmd_lazy "$@"; }
# in its body, calling to `autocmd-lazy` will trigger a unhandled
# function name event, so our lazy-loader can capture and try 
# loading autocmd-lazy.sh or autocmd_lazy.sh in `lazy` folders.

And its real body is:

# autocmd-lazy.sh
autocmd_lazy() {
  : # your implementations here
}

Why we split a lazy function into two implementations?

Because the first function auto-imported, autocmd, can be recoganized with zsh command container in .zshrc initializing phrase. So it is visible at typing command characters on zsh command-line. That means, zsh-autocompletion, autosuggestions and others machanisms can work properly.

At same time, its body is so tiny so it doesn't waste too much in zsh initializing time.

And the really implementation of autocmd was been moved into autocmd-lazy function, it will be loaded until user typed autocmd<ENTER> at first time.

Like Kebab style?

A classical bash composer would be like kebab naming as function names.

# bash only
function cleanup-homebrew() {
  :
}

But it's invalid name in zsh env. It's sadly.

Fortunately, there is a way to keep kebab name working in zsh: alias. So you may make a copy of a standard zsh function:

# for both bash and zsh
function cleanup_homebrew() {
  :
}
alias cleanup-homebrew=cleanup_homebrew

That's a trick!

Samples

Sample

Knives Document

in_debug

in debug mode?

toggle environment variable DEBUG to switch between normal_mode and debug_mode.

is_debug && echo 'debug mode' || echo 'normal mode'

debug $*, dbg, tip, err

Prints string as darker text for debugging (if env var DEBUG == 1). In normal mode, the string message will be stripped.

debug I am buggy but you don't know
debug 'I am buggy but you don'''t know'
debug "I am buggy but you don't know"

dbg "debug line"

tip "A simple message to tip you something happened: $event"
err "Error occurred whlie executed the command line: $cmd '$@'"
  • tip and err will always prints message.
  • err will prints message to stderr device.
  • dbg available on DEBUG=1, it's slight differant with debug

You may use the splitted version: debug_begin and debug_end:

if ((DEBUG)); then
  debug_begin
  cat /etc/os_release # this file will be printed with darker color.
  debug_end
fi

headline $*

print a hilight message string.

headline here is the hilighted title

is_bash & is_zsh

check if running under bash/zsh interpretor or not

is_bash && echo 'in bash'
is_zsh && echo 'in zsh'

is_linux, is_darwin

check if running in Linux/macOS shell.

is_linux && echo 'in linux'
is_darwin && grep -E 'LISTEN|UDP' somefile || grep -P 'LISTEN|UDP' somefile

UPDATED

More testers added: is_yum, is_dnf, is_apt,
is_debian_series, is_redhat_series,
is_debian, is_ubuntu, is_centod, is_fedora, is_redhat,
is_nix, ...

realpathx

cross impl for linux realpath.

Environment Variables

DEBUG = {1|0}

HAS_END = {true|:|false}

CD: directory of bash.sh

SCRIPT: full path of bash.sh

License

MIT for free.

Enjoy It!