bash.sh
is a starter template for shell developing.
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,...}
.
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.
- v20241021
- improved is_git_dirty
- added if_systemd, if_sysv (init), if_upstart
- CHANGELOG
# 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
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
curl -sSL https://github.com/hedzr/bash.sh/raw/refs/heads/master/installer | bash -s
installer
will copybash.config
to~/.local/bin/bash.sh
.
installer
makes a symbolic linkops
tobash.config
, you can run the builtin functions with it: tryops debug-info
to ensure it works.
Copy bash.config
to /usr/local/bin/
or anywhere you prefer, and source
it from your script file.
Some examples here.
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):
/path/to/bash.sh/ops.d/*.sh
/path/to/bash.sh/ops.d/{darwin,windows,ubuntu,...}/*.sh
/path/to/bash.sh/ops.d/{brew,apt,dnf,yum,...}*.sh
$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:
$HOME/.local/bin/.zsh/lazy/
$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 savevagrant_run.sh
into~/.local/bin/.zsh/lazy
, and run it:vm ls vm run u20s.local vm sizesfunction
vm
(in vm.sh) will be invoked directly, and functionvagrant_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).
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
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
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.
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.
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!
in debug mode?
toggle environment variable DEBUG
to switch between normal_mode
and debug_mode
.
is_debug && echo 'debug mode' || echo 'normal mode'
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
anderr
will always prints message.err
will prints message to stderr device.dbg
available on DEBUG=1, it's slight differant withdebug
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
print a hilight message string.
headline here is the hilighted title
check if running under bash/zsh interpretor or not
is_bash && echo 'in bash'
is_zsh && echo 'in zsh'
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, ...
cross impl for linux realpath
.
MIT for free.
Enjoy It!