diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 18:33:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 18:33:23 +0000 |
commit | 1d5cace9db9aef76f26b2d7ba54bbb76443b00b2 (patch) | |
tree | 314a15dd1aa103da13bdc83ba1d2105a290bc5ba /examples/functions | |
parent | Initial commit. (diff) | |
download | bash-89791e9f494bebe33a9c52292a98f4f079926078.tar.xz bash-89791e9f494bebe33a9c52292a98f4f079926078.zip |
Adding upstream version 5.0.upstream/5.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'examples/functions')
36 files changed, 3108 insertions, 0 deletions
diff --git a/examples/functions/array-stuff b/examples/functions/array-stuff new file mode 100644 index 0000000..e6316c7 --- /dev/null +++ b/examples/functions/array-stuff @@ -0,0 +1,122 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1999 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# usage: reverse arrayname +reverse() +{ + local -a R + local -i i + local rlen temp + + # make r a copy of the array whose name is passed as an arg + eval R=\( \"\$\{$1\[@\]\}\" \) + + # reverse R + rlen=${#R[@]} + + for ((i=0; i < rlen/2; i++ )) + do + temp=${R[i]} + R[i]=${R[rlen-i-1]} + R[rlen-i-1]=$temp + done + + # and assign R back to array whose name is passed as an arg + eval $1=\( \"\$\{R\[@\]\}\" \) +} + +A=(1 2 3 4 5 6 7) +echo "${A[@]}" +reverse A +echo "${A[@]}" +reverse A +echo "${A[@]}" + +# unset last element of A +alen=${#A[@]} +unset A[$alen-1] +echo "${A[@]}" + +# ashift -- like shift, but for arrays + +ashift() +{ + local -a R + local n + + case $# in + 1) n=1 ;; + 2) n=$2 ;; + *) echo "$FUNCNAME: usage: $FUNCNAME array [count]" >&2 + exit 2;; + esac + + # make r a copy of the array whose name is passed as an arg + eval R=\( \"\$\{$1\[@\]\}\" \) + + # shift R + R=( "${R[@]:$n}" ) + + # and assign R back to array whose name is passed as an arg + eval $1=\( \"\$\{R\[@\]\}\" \) +} + +ashift A 2 +echo "${A[@]}" + +ashift A +echo "${A[@]}" + +ashift A 7 +echo "${A[@]}" + +# Sort the members of the array whose name is passed as the first non-option +# arg. If -u is the first arg, remove duplicate array members. +array_sort() +{ + local -a R + local u + + case "$1" in + -u) u=-u ; shift ;; + esac + + if [ $# -eq 0 ]; then + echo "array_sort: argument expected" >&2 + return 1 + fi + + # make r a copy of the array whose name is passed as an arg + eval R=\( \"\$\{$1\[@\]\}\" \) + + # sort R + R=( $( printf "%s\n" "${A[@]}" | sort $u) ) + + # and assign R back to array whose name is passed as an arg + eval $1=\( \"\$\{R\[@\]\}\" \) + return 0 +} + +A=(3 1 4 1 5 9 2 6 5 3 2) +array_sort A +echo "${A[@]}" + +A=(3 1 4 1 5 9 2 6 5 3 2) +array_sort -u A +echo "${A[@]}" diff --git a/examples/functions/array-to-string b/examples/functions/array-to-string new file mode 100644 index 0000000..0d2fbe5 --- /dev/null +++ b/examples/functions/array-to-string @@ -0,0 +1,15 @@ +#! /bin/bash + +# Format: array_to_string vname_of_array vname_of_string separator +array_to_string() +{ + (( ($# < 2) || ($# > 3) )) && { + "$FUNCNAME: usage: $FUNCNAME arrayname stringname [separator]" + return 2 + } + + local array=$1 string=$2 + ((3==$#)) && [[ $3 = ? ]] && local IFS="${3}${IFS}" + eval $string="\"\${$array[*]}\"" + return 0 +} diff --git a/examples/functions/arrayops.bash b/examples/functions/arrayops.bash new file mode 100644 index 0000000..d34353a --- /dev/null +++ b/examples/functions/arrayops.bash @@ -0,0 +1,146 @@ +# arrayops.bash --- hide some of the nasty syntax for manipulating bash arrays +# Author: Noah Friedman <friedman@splode.com> +# Created: 2016-07-08 +# Public domain + +# $Id: arrayops.bash,v 1.3 2016/07/28 15:38:55 friedman Exp $ + +# Commentary: + +# These functions try to tame the syntactic nightmare that is bash array +# syntax, which makes perl's almost look reasonable. +# +# For example the apush function below lets you write: +# +# apush arrayvar newval +# +# instead of +# +# ${arrayvar[${#arrayvar[@]}]}=newval +# +# Because seriously, you've got to be kidding me. + +# These functions avoid the use of local variables as much as possible +# (especially wherever modification occurs) because those variable names +# might shadow the array name passed in. Dynamic scope! + +# Code: + +#:docstring apush: +# Usage: apush arrayname val1 {val2 {...}} +# +# Appends VAL1 and any remaining arguments to the end of the array +# ARRAYNAME as new elements. +#:end docstring: +apush() +{ + eval "$1=(\"\${$1[@]}\" \"\${@:2}\")" +} + +#:docstring apop: +# Usage: apop arrayname {n} +# +# Removes the last element from ARRAYNAME. +# Optional argument N means remove the last N elements. +#:end docstring: +apop() +{ + eval "$1=(\"\${$1[@]:0:\${#$1[@]}-${2-1}}\")" +} + +#:docstring aunshift: +# Usage: aunshift arrayname val1 {val2 {...}} +# +# Prepends VAL1 and any remaining arguments to the beginning of the array +# ARRAYNAME as new elements. The new elements will appear in the same order +# as given to this function, rather than inserting them one at a time. +# +# For example: +# +# foo=(a b c) +# aunshift foo 1 2 3 +# => foo is now (1 2 3 a b c) +# but +# +# foo=(a b c) +# aunshift foo 1 +# aunshift foo 2 +# aunshift foo 3 +# => foo is now (3 2 1 a b c) +# +#:end docstring: +aunshift() +{ + eval "$1=(\"\${@:2}\" \"\${$1[@]}\")" +} + +#:docstring ashift: +# Usage: ashift arrayname {n} +# +# Removes the first element from ARRAYNAME. +# Optional argument N means remove the first N elements. +#:end docstring: +ashift() +{ + eval "$1=(\"\${$1[@]: -\${#$1[@]}+${2-1}}\")" +} + +#:docstring aset: +# Usage: aset arrayname idx newval +# +# Assigns ARRAYNAME[IDX]=NEWVAL +#:end docstring: +aset() +{ + eval "$1[\$2]=${@:3}" +} + +#:docstring aref: +# Usage: aref arrayname idx {idx2 {...}} +# +# Echoes the value of ARRAYNAME at index IDX to stdout. +# If more than one IDX is specified, each one is echoed. +# +# Unfortunately bash functions cannot return arbitrary values in the usual way. +#:end docstring: +aref() +{ + eval local "v=(\"\${$1[@]}\")" + local x + for x in ${@:2} ; do echo "${v[$x]}"; done +} + +#:docstring aref: +# Usage: alen arrayname +# +# Echoes the length of the number of elements in ARRAYNAME. +# +# It also returns number as a numeric value, but return values are limited +# by a maximum of 255 so don't rely on this unless you know your arrays are +# relatively small. +#:end docstring: +alen() +{ + eval echo "\${#$1[@]}" + eval return "\${#$1[@]}" +} + +#:docstring anreverse: +# Usage: anreverse arrayname +# +# Reverse the order of the elements in ARRAYNAME. +# The array variable is altered by this operation. +#:end docstring: +anreverse() +{ + eval set $1 "\"\${$1[@]}\"" + eval unset $1 + while [ $# -gt 1 ]; do + eval "$1=(\"$2\" \"\${$1[@]}\")" + set $1 "${@:3}" + done +} + +#provide arrayops + +# arrayops.bash ends here diff --git a/examples/functions/autoload b/examples/functions/autoload new file mode 100644 index 0000000..cb3a673 --- /dev/null +++ b/examples/functions/autoload @@ -0,0 +1,111 @@ +# +# An almost ksh-compatible `autoload'. A function declared as `autoload' will +# be read in from a file the same name as the function found by searching the +# $FPATH (which works the same as $PATH), then that definition will be run. +# +# To do this without source support, we define a dummy function that, when +# executed, will load the file (thereby re-defining the function), then +# execute that newly-redefined function with the original arguments. +# +# It's not identical to ksh because ksh apparently does lazy evaluation +# and looks for the file to load from only when the function is referenced. +# This one requires that the file exist when the function is declared as +# `autoload'. +# +# usage: autoload func [func...] +# +# The first cut of this was by Bill Trost, trost@reed.bitnet +# +# Chet Ramey +# chet@ins.CWRU.Edu + +# +# Declare a function ($1) to be autoloaded from a file ($2) when it is first +# called. This defines a `temporary' function that will `.' the file +# containing the real function definition, then execute that new definition with +# the arguments given to this `fake' function. The autoload function defined +# by the file and the file itself *must* be named identically. +# + +aload() +{ + eval $1 '() { . '$2' ; '$1' "$@" ; return $? ; }' +} + +# +# Search $FPATH for a file the same name as the function given as $1, and +# autoload the function from that file. There is no default $FPATH. +# + +autoload() +{ + # + # Save the list of functions; we're going to blow away the arguments + # in a second. If any of the names contain white space, TFB. + # + + local args="$*" + + # + # This should, I think, list the functions marked as autoload and not + # yet defined, but we don't have enough information to do that here. + # + if [ $# -eq 0 ] ; then + echo "usage: autoload function [function...]" >&2 + return 1 + fi + + # + # If there is no $FPATH, there is no work to be done + # + + if [ -z "$FPATH" ] ; then + echo autoload: FPATH not set or null >&2 + return 1 + fi + + # + # This treats FPATH exactly like PATH: a null field anywhere in the + # FPATH is treated the same as the current directory. + # + # The path splitting command is taken from Kernighan and Pike + # + +# fp=$(echo $FPATH | sed 's/^:/.:/ +# s/::/:.:/g +# s/:$/:./ +# s/:/ /g') + + # replaced with builtin mechanisms 2001 Oct 10 + + fp=${FPATH/#:/.:} + fp=${fp//::/:.:} + fp=${fp/%:/:.} + fp=${fp//:/ } + + for FUNC in $args ; do + # + # We're blowing away the arguments to autoload here... + # We have to; there are no arrays (well, there are, but + # this doesn't use them yet). + # + set -- $fp + + while [ $# -ne 0 ] ; do + if [ -f $1/$FUNC ] ; then + break # found it! + fi + shift + done + + if [ $# -eq 0 ] ; then + echo "$FUNC: autoload function not found" >&2 + continue + fi + +# echo auto-loading $FUNC from $1/$FUNC + aload $FUNC $1/$FUNC + done + + return 0 +} diff --git a/examples/functions/autoload.v2 b/examples/functions/autoload.v2 new file mode 100644 index 0000000..e8f3433 --- /dev/null +++ b/examples/functions/autoload.v2 @@ -0,0 +1,192 @@ +# +# An almost ksh-compatible `autoload'. A function declared as `autoload' will +# be read in from a file the same name as the function found by searching the +# $FPATH (which works the same as $PATH), then that definition will be run. +# +# To do this without source support, we define a dummy function that, when +# executed, will load the file (thereby re-defining the function), then +# execute that newly-redefined function with the original arguments. +# +# It's not identical to ksh because ksh apparently does lazy evaluation +# and looks for the file to load from only when the function is referenced. +# This one requires that the file exist when the function is declared as +# `autoload'. +# +# usage: autoload [-pu] [func ...] +# +# options: +# -p print in a format that can be reused as input +# -u unset each function and remove it from the autoload list +# +# The first cut of this was by Bill Trost, trost@reed.edu +# +# Chet Ramey +# chet@ins.CWRU.Edu + +unset _AUTOLOADS +_aindex=0 + +# +# Declare a function ($1) to be autoloaded from a file ($2) when it is first +# called. This defines a `temporary' function that will `.' the file +# containg the real function definition, then execute that new definition with +# the arguments given to this `fake' function. The autoload function defined +# by the file and the file itself *must* be named identically. +# + +_aload() +{ + eval $1 '() { . '$2' ; '$1' "$@" ; return $? ; }' + _autoload_addlist "$1" +} + +_autoload_addlist() +{ + local i=0 + + while (( i < $_aindex )); do + case "${_AUTOLOADS[i]}" in + "$1") return 1 ;; + esac + (( i += 1 )) + done + _AUTOLOADS[_aindex]="$1" + (( _aindex += 1 )) + return 0 +} + +_autoload_dump() +{ + local func + + for func in ${_AUTOLOADS[@]}; do + [ -n "$1" ] && echo -n "autoload " + echo "$func" + done +} + +# Remove $1 from the list of autoloaded functions +_autoload_remove_one() +{ + local i=0 nnl=0 + local -a nlist + + while (( i < _aindex )); do + case "${_AUTOLOADS[i]}" in + "$1") ;; + *) nlist[nnl]="${_AUTOLOADS[i]}" ; (( nnl += 1 ));; + esac + (( i += 1 )) + done + unset _AUTOLOADS _aindex + eval _AUTOLOADS=( ${nlist[@]} ) + _aindex=$nnl +} + +# Remove all function arguments from the list of autoloaded functions +_autoload_remove() +{ + local func i es=0 + + # first unset the autoloaded functions + for func; do + i=0 + while (( i < _aindex )); do + case "${_AUTOLOADS[i]}" in + "$func") unset -f $func ; break ;; + esac + (( i += 1 )) + done + if (( i == _aindex )); then + echo "autoload: $func: not an autoloaded function" >&2 + es=1 + fi + done + + # then rebuild the list of autoloaded functions + for func ; do + _autoload_remove_one "$func" + done + + return $es +} + +# +# Search $FPATH for a file the same name as the function given as $1, and +# autoload the function from that file. There is no default $FPATH. +# + +autoload() +{ + local -a fp + local _autoload_unset nfp i + + if (( $# == 0 )) ; then + _autoload_dump + return 0 + fi + + OPTIND=1 + while getopts pu opt + do + case "$opt" in + p) _autoload_dump printable; return 0;; + u) _autoload_unset=y ;; + *) echo "autoload: usage: autoload [-pu] [function ...]" >&2 + return 1 ;; + esac + done + + shift $(( $OPTIND - 1 )) + + if [ -n "$_autoload_unset" ]; then + _autoload_remove "$@" + return $? + fi + + # + # If there is no $FPATH, there is no work to be done + # + + if [ -z "$FPATH" ] ; then + echo "autoload: FPATH not set or null" >&2 + return 1 + fi + + # + # This treats FPATH exactly like PATH: a null field anywhere in the + # FPATH is treated the same as the current directory. + # + # This turns $FPATH into an array, substituting `.' for `' + # + eval fp=( $( + IFS=':' + set -- ${FPATH} + for p in "$@" ; do echo -n "${p:-.} "; done + ) + ) + + nfp=${#fp[@]} + + for FUNC ; do + i=0; + while (( i < nfp )) ; do + if [ -f ${fp[i]}/$FUNC ] ; then + break # found it! + fi + (( i += 1 )) + done + + if (( i == nfp )) ; then + echo "autoload: $FUNC: autoload function not found" >&2 + es=1 + continue + fi + +# echo auto-loading $FUNC from ${fp[i]}/$FUNC + _aload $FUNC ${fp[i]}/$FUNC + es=0 + done + + return $es +} diff --git a/examples/functions/autoload.v3 b/examples/functions/autoload.v3 new file mode 100644 index 0000000..b1e5dfe --- /dev/null +++ b/examples/functions/autoload.v3 @@ -0,0 +1,125 @@ +#From: Mark Kennedy <mark.t.kennedy@gmail.com> (<mtk@ny.ubs.com>) +#Message-ID: <35E2B899.63A02DF5@ny.ubs.com> +#Date: Tue, 25 Aug 1998 09:14:01 -0400 +#To: chet@nike.ins.cwru.edu +#Subject: a newer version of the ksh-style 'autoload' + +#enclosed you'll find 'autoload.v3', a version of the autoloader +#that emulates the ksh semantics of delaying the resolution (and loading) of the function +#until its first use. i took the liberty of simplifying the code a bit although it still uses the +#same functional breakdown. i recently went through the exercise of converting +#my ksh-based environment to bash (a very, very pleasant experience) +#and this popped out. + +# the psuedo-ksh autoloader. + +# The first cut of this was by Bill Trost, trost@reed.bitnet. +# The second cut came from Chet Ramey, chet@ins.CWRU.Edu +# The third cut came from Mark Kennedy, mtk@ny.ubs.com. 1998/08/25 + +unset _AUTOLOADS + +_aload() +{ + local func + for func; do + eval $func '() + { + local f=$(_autoload_resolve '$func') + if [[ $f ]]; then + . $f + '$func' "$@" + return $? + else + return 1 + fi + }' + _autoload_addlist $func + done +} + +_autoload_addlist() +{ + local func + + for func in ${_AUTOLOADS[@]}; do + [[ $func = "$1" ]] && return + done + + _AUTOLOADS[${#_AUTOLOADS[@]}]=$1 +} + +_autoload_dump() +{ + local func + + for func in ${_AUTOLOADS[@]}; do + [[ $1 ]] && echo -n "autoload " + echo $func + done +} + +_autoload_remove_one() +{ + local func + local -a NEW_AUTOLOADS + + for func in ${_AUTOLOADS[@]}; do + [[ $func != "$1" ]] && NEW_AUTOLOADS[${#NEW_AUTOLOADS[@]}]=$func + done + + _AUTOLOADS=( ${NEW_AUTOLOADS[@]} ) +} + +_autoload_remove() +{ + local victim func + + for victim; do + for func in ${_AUTOLOADS[@]}; do + [[ $victim = "$func" ]] && unset -f $func && continue 2 + done + echo "autoload: $func: not an autoloaded function" >&2 + done + + for func; do + _autoload_remove_one $func + done +} + +_autoload_resolve() +{ + if [[ ! "$FPATH" ]]; then + echo "autoload: FPATH not set or null" >&2 + return + fi + + local p + + for p in $( (IFS=':'; set -- ${FPATH}; echo "$@") ); do + p=${p:-.} + if [ -f $p/$1 ]; then echo $p/$1; return; fi + done + + echo "autoload: $1: function source file not found" >&2 +} + +autoload() +{ + if (( $# == 0 )) ; then _autoload_dump; return; fi + + local opt OPTIND + + while getopts pu opt + do + case $opt in + p) _autoload_dump printable; return;; + u) shift $((OPTIND-1)); _autoload_remove "$@"; return;; + *) echo "autoload: usage: autoload [-pu] [function ...]" >&2; return;; + esac + done + + shift $(($OPTIND-1)) + + _aload "$@" +} diff --git a/examples/functions/autoload.v4 b/examples/functions/autoload.v4 new file mode 100644 index 0000000..a172e61 --- /dev/null +++ b/examples/functions/autoload.v4 @@ -0,0 +1,556 @@ +## -*- sh -*- + +# The psuedo-ksh autoloader. + +# How to use: +# o One function per file. +# o File and function name match exactly. +# o File is located in a directory that is in FPATH. +# o This script (autoload) must be sourced in as early as possible. This +# implies that any code in this script should NOT rely on any library of local +# or self-defined functions having already been loaded. +# o autoload must be called for each function before the function can be used. If +# autoloads are in directories where there are nothing but autoloads, then +# 'autoload /path/to/files/*' suffices (but see options -a and -f). +# o The call must be made in the current environment, not a subshell. +# o The command line suffices as "current environment". If you have autoload +# calls in a script, that script must be dotted into the process. + +# The first cut of this was by Bill Trost, trost@reed.bitnet. +# The second cut came from Chet Ramey, chet@ins.CWRU.Edu +# The third cut came from Mark Kennedy, mtk@ny.ubs.com. 1998/08/25 +# The fourth cut came from Matthew Persico, matthew.persico@gmail.com 2017/August + +autoload_calc_shimsize () +{ + echo $((AUTOLOAD_SHIM_OVERHEAD + 3 * ${#1})) +} + +_autoload_split_fpath () +{ + (IFS=':'; set -- ${FPATH}; echo "$@") +} + +_aload() +{ + local opt OPTIND + local doexport=0 + local doreload=0 + local doverbose=0 + local doevalshim=0 + local loadthese + local optimize=0 + local loaded=0 + local exported=0 + local optimized=0 + local summary=0 + local dofpath=0 + while getopts xrvla:oyf opt; do + case $opt in + x) doexport=1;; + r) doreload=1;; + v) doverbose=1;; + l) doevalshim=1;; + a) loadthese=$(find $OPTARG -maxdepth 1 -type f -printf '%f ');; + o) optimize=1;; + y) summary=1;; + f) loadthese=$(find $(_autoload_split_fpath) -maxdepth 1 -type f -printf '%f ');; + *) echo "_aload: usage: _aload [-xrvlyf] [-a dir] [function ...]" >&2; return;; + esac + done + + shift $(($OPTIND-1)) + + [ -z "$loadthese" ] && loadthese="$@" + + local func + for func in $loadthese; do + local exists_fn + exists_fn=$(declare -F $func) + if [ -n "$exists_fn" ] && ((doreload==0)) && ((doevalshim==0)) + then + if ((doverbose)) + then + echo "autoload: function '$func' already exists" + fi + else + local andevaled='' + local andexported='' + local evalstat=0 + local doshim=1 + local funcfile + funcfile=$(_autoload_resolve $func) + if [[ $funcfile ]] ; then + ## The file was found for $func. Process it. + + if ((optimize)); then + ## For the first function loaded, we will not know + ## AUTOLOAD_SHIM_OVERHEAD. We can only calculate it after + ## we have loaded one function. + if [[ $AUTOLOAD_SHIM_OVERHEAD ]]; then + local size=$(wc -c $funcfile| sed 's/ .*//') + local shimsize=$(autoload_calc_shimsize $func) + if (( size <= shimsize)); then + doshim=0 + andevaled=', optimized' + ((optimized+=1)) + fi + fi + fi + + if ((doevalshim)); then + doshim=0 + andevaled=', evaled' + fi + + ## 'brand' as in branding a cow with a mark. We add a local + ## variable to each function we autoload so that we can tell + ## later on it is an autoloaded function without having to + ## maintain some bash array or hash that cannot be passed to + ## and used by subshells. + local brandtext + brandtext="eval \"\$(type $func | sed -e 1d -e 4ilocal\\ AUTOLOADED=\'$func\')\"" + if ((doshim)); then + ## Don't bother trying to save space by shoving all the + ## eval text below onto one unreadable line; new lines will + ## be added at your semicolons and any indentation below + ## seems to be ignored anyway if you export the function; + ## look at its BASH_FUNCTION representation. + eval $func '() + { + local IS_SHIM="$func" + local file=$(_autoload_resolve '$func') + if [[ $file ]] + then + . $file + '$brandtext' + '$func' "$@" + return $? + else + return 1; + fi + }' + else + . $funcfile + eval "$brandtext" + fi + evalstat=$? + if((evalstat==0)) + then + ((loaded+=1)) + ((doexport)) && export -f $func && andexported=', exported' && ((exported+=1)) + ((doverbose)) && echo "$func autoloaded${andexported}${andevaled}" + if [[ ! $AUTOLOAD_SHIM_OVERHEAD ]] && ((doshim)); then + ## ...we have just loaded the first function shim into + ## memory. Let's calc the AUTOLOAD_SHIM_OVERHEAD size + ## to use going forward. In theory, we could check + ## again here to see if we should optimize and source + ## in this function, now that we now the + ## AUTOLOAD_SHIM_OVERHEAD. In practice, it's not worth + ## duping that code or creating a function to do so for + ## one function. + AUTOLOAD_SHIM_OVERHEAD=$(type $func | grep -v -E "^$1 is a function" | sed "s/$func//g"| wc -c) + export AUTOLOAD_SHIM_OVERHEAD + fi + else + echo "$func failed to load" >&2 + fi + fi + fi + done + ((summary)) && echo "autoload: loaded:$loaded exported:$exported optimized:$optimized overhead:$AUTOLOAD_SHIM_OVERHEAD bytes" +} + +_autoload_dump() +{ + local opt OPTIND + local opt_p='' + local opt_s='' + while getopts ps opt + do + case $opt in + p ) opt_p=1;; + s ) opt_s=1;; + esac + done + + shift $(($OPTIND-1)) + + local exported='' + local executed='' + local func + for func in $(declare | grep -E 'local\\{0,1} AUTOLOADED' | sed -e "s/.*AUTOLOADED=//" -e 's/\\//g' -e 's/[");]//g' -e "s/'//g") + do + if [ -n "$opt_p" ]; then echo -n "autoload "; fi + if [ -n "$opt_s" ] + then + exported=$(declare -F | grep -E "${func}$" | sed 's/declare -f\(x\{0,1\}\).*/\1/') + [ "$exported" = 'x' ] && exported=' exported' || exported=' not exported' + executed=$(type $func | grep 'local IS_SHIM') + [ -z "$executed" ] && executed=' executed' || executed=' not executed' + fi + echo "${func}${exported}${executed}" + done +} + +_autoload_resolve() +{ + if [[ ! "$FPATH" ]]; then + echo "autoload: FPATH not set or null" >&2 + return + fi + + local p # for 'path'. The $() commands in the for loop split the FPATH + # string into its constituents so that each one may be processed. + + for p in $( _autoload_split_fpath ); do + p=${p:-.} + if [ -f $p/$1 ]; then echo $p/$1; return; fi + done + + echo "autoload: $1: function source file not found" >&2 +} + +_autoload_edit() +{ + [ -z "$EDITOR" ] && echo "Error: no EDITOR defined" && return 1 + local toedit + local func + for func in "$@" + do + local file=$(_autoload_resolve $func) + if [[ $file ]] + then + toedit="$toedit $file" + else + echo "$funcname not found in FPATH funcfile. Skipping." + fi + done + + [ -z "$toedit" ] && return 1 + + local timemarker=$(mktemp) + + $EDITOR $toedit + + local i + for i in $toedit + do + if [ $i -nt $timemarker ] + then + local f=$(basename $i) + echo Reloading $f + autoload -r $f + fi + done +} + +_autoload_page() +{ + [ -z "$PAGER" ] && echo "Error: no PAGER defined" && return 1 + local topage + local func + for func in "$@" + do + local file=$(_autoload_resolve $func) + if [[ $file ]] + then + topage="$topage $file" + else + echo "$funcname not found in FPATH funcfile. Skipping." + fi + done + + [ -z "$topage" ] && return 1 + + $PAGER $topage +} + +_autoload_remove() +{ + unset -f "$@" +} + +_autoload_help() +{ + cat <<EOH +NAME + autoload + +SYNOPSIS + autoload [-ps] + autoload [-xuremloyv] [function ...] + autoload -a directory [-oyv] + autoload -f [-oyv] + autoload [-h] + + autoreload [function ...] + +DESCRIPTION + + An implementation of the 'autoload' functionality built into other + shells, of which 'ksh' is the most prominent. It allows for a keeping + the process environment small by loading small 'shim' functions into + memory that will, on first call, load the full text of the given + function and run it. Subsequent calls to the function just run the + function. + + 'autoreload' is a synonym for 'autoload -r'. See below. + +USAGE + + o Each function to be autoloaded should be defined in a single file, + named exactly the same as the function. + + o In order to avoid side effects, do NOT put code other than the + function definition in the file. Unless of course you want to do some + one-time initialization. But beware that if you reload the function + for any reason, you will rerun the initialization code. Make sure + your initialization is re-entrant. Or, better yet, + + *** do NOT put code other than the function definition in the file *** + + o These function definition files should be placed in a directory that + is in the FPATH environment variable. Subdirectories are NOT scanned. + + o The autoload script should be sourced into the current process as + early as possible in process start up. See NOTES below for + suggestions. + + o The calls to the autoload function must be made in the current + process. If your calls are in their own script, that script must be + sourced in. Command line invocations are also sufficient. (But see + '-l' below.) + + o The first time the function is called, the shim function that was + created by the 'autoload' call is what is executed. This function + then goes and finds the appropriate file in FPATH, sources it in and + then calls the actual function with any arguments you just passed in + to the shim function. Subsequent calls just run the function. + +OPTIONS + + -a Autoload (a)ll the functions found in the given directory. + + -f Autoload all the functions found in all the directories on the + FPATH. + + -p Print all the autoloaded functions. + + -s Print all the autoloaded functions and add their export status. + + -x Export the specified functions to the environment for use in + subshells. + + -u Unset the function, so it can be reloaded. + + -r Reload the shims of the specified functions, even if the functions + have been already been executed. This will allow you to modify the + functions' source and have the new version executed next time the + function is called. + + It would be very easy to modify a function's script, run the + function and scratch your head for a long time trying to figure out + why your changes are not being executed. That's why we provide the + '-e' flag described below for modifications. + + Reloads, of course, only apply in the context of the current session + and any future subshell you start from the current session. Existing + sessions will need to have the same 'autoload -r' command run in + them. + + -e Find the scripts in which the specified functions are defined and + start up \$EDITOR on those scripts. Reload the ones that were + modified when you exit \$EDITOR. (Note: If you use 'autoload -e foo' + to edit function 'foo', and then in your editor you separately load + up function 'bar', 'autoload' has no way of knowing that you edited + 'bar' and will NOT reload 'bar' for you.) + + Reloads, of course, only apply in the context of the current session + and any future subshell you start from the current session. Existing + sessions will need to have the same 'autoload -r' command run in + them. + + -m Find the scripts in which the specified functions are defined and + run \$PAGER on them ('m' is for 'more', because 'p' (page) and 'l' + (load) are already used as options in 'autoload'). + + -l When autoloading a function, eval the shim immediately in order to + load the true function code. See "Using '-l'" in the NOTES below for + details. + + -o Optimize. When autoloading, take the time to execute + + 'theCharCount=\$(wc -c \$theFuncFile)' + + for each funcion and + + if \$theCharCount < \$AUTOLOAD_SHIM_OVERHEAD + + don't shim it, just eval directly. + + -y Summar(y). Print the number of loaded, exported and optimized + functions. + + -v Turns up the chattiness. + +NOTES + + o Calling 'autoload' on a function that already exists (either shimmed + or expanded) silently ignores the request to load the shim unless it + has been previously removed (-u) or you force the reload (-r). + + o Changing and reloading a function that has been exported does not + require it be re-exported; the modifications will appear in + subsequent subshells. + + o Using '-1' + + If you are running under set -x and/or set -v, you may see that the + shim does not appear to "work"; instead of seeing the shim first and + the real code subsequently, you may see the shim evaluated multiple + times. + + This may not be an error; review your code. What is most likely + happening is that you are calling the function in subshells via + backticks or $(), or in a script that is not being sourced into the + current environment. If you have not previously called the function + in question at your command line or in a script that was sourced into + the current envirnoment, then the various subshells are going to + encounter the shim and replace with the real code before executing. + + Remember, however, that environment modifications that occur in a + subshell are NOT propagated back to the calling shell or over to any + sibling shells. So, if you call an autoloaded function in a very + tight loop of very many subshells, you may want to make an 'autoload + -l' call before you start your loop. '-l' will instruct 'autoload' to + bypass the shim creation and just source in the function's file + directly. For a few calls, the overhead of repeatedly running the + shim is not expensive, but in a tight loop, it might be. Caveat + Programer. + + o Although the number of functions in the environment does not change + by using 'autoload', the amount of memory they take up can be greatly + reduced, depending on the size of your functions. If you have a lot + of small functions, then it is possible that the shim text will be + larger than your actual functions, rendering the memory savings moot. + + 'small' in this case can be determined by calling the function + 'autoload_calc_shimsize' with the name of the function to determine + its shim size. + + o In order to support the -p and -s options, we need a way to determine + if a function 'func' has been autoloaded or if it was loaded + diredctly. In order to do that, we modify the function's code by + adding the text + + local AUTOLOADED='func'; + + to the shim and to the actual function text, just after the opening + brace. Then supporting -p and -s is just a matter of grepping through + all the function text in memory. Even though grepping through the + environment may not be the most efficient way to support this, it is + the simplest to implement for -p and -s operations that are not + heavily used. + + As a consquence of this (and other reasons), the AUTOLOAD* namespace + is reserved for autoloading. Make sure you check any functions that + you bring under autoload for use of variables or functions that start + with AUTOLOAD and change them. + + o The easiest way to load shims for all functions on the FPATH is to run + + autoload -f -x + + in the profile that gets run for login shells. + + When called in the profile of a login shell where no definitions + exist, -f will load all functions it can find on FPATH and -x will + export all of those functions to be available in subshells when this + is called in a login shell. Using this option will relieve you of the + need to call 'autoload' after Every Single Function Definition, nor + will you need to call it in subshells. + + The only thing left to do is to load up the autoload function itself + and its helper functions. That needs to happen in your profile: + + export FPATH=~/functions # or wherever you stash them + if [ -z $(declare -F autoload) ] + then + . ~/bin/autoload # or wherever you've put it + fi + + The 'if' statement is used to make sure we don't reload autoload + needlessly. Sourcing in the autoload script loads the 'autoload' + function and all of its support functions. Additionally, we export + all of these functions so that they are available in subshells; you + do not have to re-source the autoload file in '.bashrc'. + + o Even with all of these shenanigans, you will find cases where no + matter how hard you try, your autoloaded functions will be + unavailable to you, even if you run 'autoload -x -f'. The typical + condition for this is starting up not a subshell, but a brand new + DIFFERENT shell. And the typical example of this is git extentions. + + At the time of this writing, git extentions work by taking a command + 'git foo' and looking for a file 'git-foo' on the path. 'git' then + executes 'git-foo' in a new shell - it executes your command in + /bin/sh. That's not a subshell of your process. It will not get your + exported shell functions. Ballgame over. + + If you find that you want your functions to be available in such + circumstances, convert them back to plain old scripts, make sure they + are 'sh' compliant and take the read/parse hit every time they are + run. + +EOH +} + +autoload() +{ + if (( $# == 0 )) ; then _autoload_dump; return; fi + + local opt OPTIND OPTARG + local passthru + local dumpopt + while getopts psuema:yxrvlohf opt + do + case $opt in + p|s) dumpopt="$dumpopt -${opt}";; + u) shift $((OPTIND-1)); _autoload_remove "$@"; return;; + e) shift $((OPTIND-1)); _autoload_edit "$@"; return;; + m) shift $((OPTIND-1)); _autoload_page "$@"; return;; + x|r|v|l|y|f|o) passthru="$passthru -$opt";; + a) passthru="$passthru -$opt $OPTARG";; + h) _autoload_help; return;; + *) echo "autoload: usage: autoload [-puUx] [function ...]" >&2; return;; + esac + done + + shift $(($OPTIND-1)) + if [ -n "$dumpopt" ] + then + _autoload_dump $dumpopt + else + _aload $passthru "$@" + fi +} + +autoreload () +{ + autoload -r "$@" +} + +## When we source in autoload, we export (but NOT autoload) the autoload +## functions so that they are available in subshells and you don't have to +## source in the autoload file in subshells. +export -f _aload \ + _autoload_dump \ + _autoload_edit \ + _autoload_help \ + _autoload_page \ + _autoload_resolve \ + _autoload_split_fpath \ + autoload \ + autoload_calc_shimsize \ + autoreload diff --git a/examples/functions/autoload.v4.t b/examples/functions/autoload.v4.t new file mode 100644 index 0000000..6d35d14 --- /dev/null +++ b/examples/functions/autoload.v4.t @@ -0,0 +1,184 @@ +#!/bin/bash + +workdir=$(mktemp -d) + +cp autoload $workdir + +cd $workdir +pwd + +. ./autoload + +funclist='ALTEST_func1 ALTEST_funcexport ALTEST_funcu' +for funcname in $funclist; do + cat <<EOFFUNC > $funcname +$funcname () +{ +echo this is $funcname + +} +EOFFUNC + +done + +export FPATH=$workdir + +autoload ALTEST_func1 ALTEST_funcu +autoload -x ALTEST_funcexport + +ok=0 +failed=0 + +for funcname in $funclist; do + + testname="$funcname loaded" + got=$(type $funcname 2>&1) + if [[ $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname" + ((failed+=1)) + else + echo "ok - $testname" + ((ok+=1)) + + testname="$funcname is a shim" + if [[ ! $got =~ "IS_SHIM" ]]; then + echo "## Failed $testname" + ((failed+=1)) + else + echo "ok - $testname" + ((ok+=1)) + + testname="$funcname shim executed" + $funcname > /dev/null + got=$(type $funcname 2>&1) + if [[ $got =~ "IS_SHIM" ]]; then + echo "## Failed $testname" + ((failed+=1)) + else + echo "ok - $testname" + ((ok+=1)) + fi + fi + fi +done + +funcname=ALTEST_func1 +testname="$funcname shim reloaded" +autoload -r $funcname +got=$(type $funcname 2>&1) +if [[ ! $got =~ "IS_SHIM" ]]; then + echo "## Failed $testname" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +funcname=ALTEST_funcu +testname="$funcname shim unloaded" +autoload -u $funcname +got=$(type $funcname 2>&1) +if [[ ! $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +testname="autoload -p" +got=$(autoload -p | grep ALTEST) +if [[ ! $got =~ "autoload ALTEST_func1" ]] || \ + [[ ! $got =~ "autoload ALTEST_funcexport" ]] ; then +echo "## Failed $testname" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +testname="autoload -s" +echo "Executing $testname, could take a long time..." +got=$(autoload -s | grep ALTEST) +if [[ ! $got =~ "ALTEST_func1 not exported not executed" ]] || \ + [[ ! $got =~ "ALTEST_funcexport exported executed" ]] ; then + echo "## Failed $testname" + echo "## got: $got" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +testname="autoload -r -a $FPATH" +autoload -r -a $FPATH +localfailed=0 +localok=0 +for funcname in $funclist; do + got=$(type $funcname 2>&1) + if [[ $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname - $funcname" + ((localfailed+=1)) + else + ((localok+=1)) + if [[ ! $got =~ "IS_SHIM" ]]; then + ((localfailed+=1)) + else + ((localok+=1)) + fi + fi +done +if ((localfailed==0)); then + echo "ok - $testname" + ((ok+=1)) +else + ((failed+=1)) +fi + +testname="autoload -u $funclist" +autoload -u $funclist +localfailed=0 +localok=0 +for funcname in $funclist; do + got=$(type $funcname 2>&1) + if [[ ! $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname - $funcname" + ((localfailed+=1)) + else + ((localok+=1)) + fi +done +if ((localfailed==0)); then + echo "ok - $testname" + ((ok+=1)) +else + ((failed+=1)) +fi + +testname="autoload -r -f" +autoload -r -f +localfailed=0 +localok=0 +for funcname in $funclist; do + got=$(type $funcname 2>&1) + if [[ $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname - $funcname" + ((localfailed+=1)) + else + ((localok+=1)) + if [[ ! $got =~ "IS_SHIM" ]]; then + ((localfailed+=1)) + else + ((localok+=1)) + fi + fi +done +if ((localfailed==0)); then + echo "ok - $testname" + ((ok+=1)) +else + ((failed+=1)) +fi + +echo $ok passed, $failed failed +exit $failed diff --git a/examples/functions/basename b/examples/functions/basename new file mode 100644 index 0000000..a541349 --- /dev/null +++ b/examples/functions/basename @@ -0,0 +1,23 @@ +# Date: Fri, 11 Oct 91 11:22:36 edt +# From: friedman@gnu.ai.mit.edu +# To: bfox@gnu.ai.mit.edu + +# A replacement for basename(1). Not all the systems I use have this +# program. Usage: basename [path] {extension} +function basename () +{ + local path="$1" + local suffix="$2" + local tpath="${path%/}" + + # Strip trailing '/' characters from path (unusual that this should + # ever occur, but basename(1) seems to deal with it.) + while [ "${tpath}" != "${path}" ]; do + tpath="${path}" + path="${tpath%/}" + done + + path="${path##*/}" # Strip off pathname + echo ${path%${suffix}} # Also strip off extension, if any. +} + diff --git a/examples/functions/csh-compat b/examples/functions/csh-compat new file mode 100644 index 0000000..b8dcf8f --- /dev/null +++ b/examples/functions/csh-compat @@ -0,0 +1,48 @@ +# C-shell compatabilty package. +# setenv VAR VALUE +function setenv () +{ + export $1="$2" +} + +function unsetenv () +{ + unset $1 +} + +# Can't write foreach yet. Need pattern matching, and a few extras. +function foreach () { +echo 'Can'\''t do `foreach'\'' yet. Type "help for".' +} + +# Make this work like csh's. Special case "term" and "path". +#set () { +#} + +chdir () +{ + builtin cd "$@" +} + +# alias - convert csh alias commands to bash functions +# from Mohit Aron <aron@cs.rice.edu> +# posted to usenet as <4i5p17$bnu@larry.rice.edu> +function alias () +{ + if [ "x$2" = "x" ] + then + declare -f $1 + else + case $2 in + *[#\!]*) + comm=$(echo $2 | sed 's/\\!\*/\"$\@\"/g + s/\\!:\([1-9]\)/\"$\1\"/g + s/#/\\#/g') + ;; + *) + comm="$2 \"\$@\"" ;; + esac + + eval function $1 \(\) "{" command "$comm" "; }" + fi +} diff --git a/examples/functions/dirname b/examples/functions/dirname new file mode 100644 index 0000000..ccb8c84 --- /dev/null +++ b/examples/functions/dirname @@ -0,0 +1,21 @@ +# Date: Fri, 11 Oct 91 11:22:36 edt +# From: friedman@gnu.ai.mit.edu +# To: bfox@gnu.ai.mit.edu + +# A replacement for dirname(1). This one appears less often on some +# systems I use than basename(1), and I really depend on it for some +# things. Usage: dirname [path] +function dirname () +{ + local dir="$1" + local tdir="${dir%/}" + + # Strip trailing '/' characters from dir (unusual that this should + # ever occur, but dirname(1) seems to deal with it.) + while [ "${tdir}" != "${dir}" ]; do + tdir="${dir}" + dir="${tdir%/}" + done + + echo "${dir%/*}" +} diff --git a/examples/functions/dirstack b/examples/functions/dirstack new file mode 100644 index 0000000..d68e619 --- /dev/null +++ b/examples/functions/dirstack @@ -0,0 +1,160 @@ +#!/bin/bash +# @(#) dirstack + +### +# Another implementation of the directory manipulation functions +# published in the Bolsky & Korn book : "The new Korn shell" : +# cd, to change current directory +# d, to display the stack content +# Eric Sanchis (eric.sanchis@iut-rodez.fr), 2012 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +### + + +shopt -s expand_aliases +shopt -s extglob +shopt -s cdable_vars + +alias integer='declare -i' + +integer MAX=32 +integer INDMAX=MAX-1 +integer INDTOP=0 + +unalias cd 2>/dev/null +alias cd=cdir + +unset tab +tab[INDTOP]="$(pwd)" + + +function cdir +{ + local -i ind + local dir + +dir="${1:-$HOME}" +case "$dir" in + - ) # cd - => equivalent to : cd -1 + ind=INDTOP-1 + cd_by_number $ind + ;; + -+([[:digit:]]) ) # cd -n + ind=$INDTOP-${dir#-} + cd_by_number $ind + ;; + *) # cd ~ or cd dir_name + cd_by_name "$dir" +esac +} + + +function cd_by_number +{ + local -i k=$1 + local -i j + local dirtmp + +if (( k < 0 )) + then + echo Impossible to change directory >&2 + return 1 + else + dirtmp="${tab[k]}" + j=k+1 + while (( j <= INDTOP )) + do + tab[j-1]="${tab[j]}" + j=j+1 + done + tab[INDTOP]="$dirtmp" + \cd "${tab[INDTOP]}" +fi +} + + +function cd_by_name +{ + local -i i + local rep + +rep=$( \cd "$1" &>/dev/null && pwd) +if [[ -z "$rep" ]] + then + echo cd : "$1" unknown >&2 + return 1 +fi + + i=$INDTOP + while (( i >= 0 )) + do + if [[ "${tab[i]}" == "$rep" ]] + then break + fi + i=i-1 + done + +if (( i == INDTOP )) + then # cd -0 => we do nothing ! + return 0 + elif (( i == -1 )) + then # the directory isn't in the stack + if (( INDTOP == INDMAX )) + then # the stack is FULL + # the oldest directory is removed + local -i m + + m=1 + while (( m <= INDMAX )) + do + tab[m-1]="${tab[m]}" + m=m+1 + done + else # the new directory is added to the top of the stack + INDTOP=INDTOP+1 + fi + tab[INDTOP]="$rep" + \cd "${tab[INDTOP]}" + return 0 + + else # the directory is already in the stack + # $i gives its index + cd_by_number $i +fi +} + + +function d # display the directory stack +{ + local -i i + local rep + +i=0 +while (( $i <= $INDTOP )) + do + rep="${tab[INDTOP-i]#$HOME/}" + case "$rep" in + $HOME) rep="~" ;; + /* ) : ;; + * ) rep="~/$rep" + esac + + echo "$i ) $rep" + i=i+1 + done +} + + diff --git a/examples/functions/exitstat b/examples/functions/exitstat new file mode 100644 index 0000000..f49ebf5 --- /dev/null +++ b/examples/functions/exitstat @@ -0,0 +1,22 @@ +# Contributed by Noah Friedman and Roland McGrath. + +# To be run by the PROMPT_COMMAND variable, so that one can see what +# the exit status of processes are. + +function check_exit_status () +{ + local status="$?" + local signal="" + + if [ ${status} -ne 0 ] && [ ${status} != 128 ]; then + # If process exited by a signal, determine name of signal. + if [ ${status} -gt 128 ]; then + signal="$(builtin kill -l $((${status} - 128)) 2>/dev/null)" + if [ "$signal" ]; then signal="($signal)"; fi + fi + echo "[Exit ${status} ${signal}]" 1>&2 + fi + return 0 +} + +PROMPT_COMMAND=check_exit_status diff --git a/examples/functions/external b/examples/functions/external new file mode 100644 index 0000000..c2e52cd --- /dev/null +++ b/examples/functions/external @@ -0,0 +1,50 @@ +# Contributed by Noah Friedman. + +# To avoid using a function in bash, you can use the `builtin' or +# `command' builtins, but neither guarantees that you use an external +# program instead of a bash builtin if there's a builtin by that name. So +# this function can be used like `command' except that it guarantees the +# program is external by first disabling any builtin by that name. After +# the command is done executing, the state of the builtin is restored. +function external () +{ + local state="" + local exit_status + + if builtin_p "$1"; then + state="builtin" + enable -n "$1" + fi + + command "$@" + exit_status=$? + + if [ "$state" = "builtin" ]; then + enable "$1" + fi + + return ${exit_status} +} + +# What is does is tell you if a particular keyword is currently enabled as +# a shell builtin. It does NOT tell you if invoking that keyword will +# necessarily run the builtin. For that, do something like +# +# test "$(builtin type -type [keyword])" = "builtin" +# +# Note also, that disabling a builtin with "enable -n" will make builtin_p +# return false, since the builtin is no longer available. +function builtin_p () +{ + local word + + set $(builtin type -all -type "$1") + + for word in "$@" ; do + if [ "${word}" = "builtin" ]; then + return 0 + fi + done + + return 1 +} diff --git a/examples/functions/fact b/examples/functions/fact new file mode 100644 index 0000000..97efd49 --- /dev/null +++ b/examples/functions/fact @@ -0,0 +1,13 @@ +# Who said shells can't use recursion? Here is a factorial function. +# You call it with a number as an argument, and it returns the factorial +# of that number. + +fact () +{ + local num=$1; + if [ "$num" = 1 ] ; then + echo 1 + return ; + fi; + echo $(( $num * $(fact $(( $num - 1 )) ) )) +} diff --git a/examples/functions/fstty b/examples/functions/fstty new file mode 100644 index 0000000..5ed594f --- /dev/null +++ b/examples/functions/fstty @@ -0,0 +1,77 @@ +# +# A function that works as a front end for both stty and the `bind' +# builtin, so the tty driver and readline see the same changes +# +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 2011 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# Convert between the stty ^H control character form and the readline \C-H +# form +# +cvt() +{ + echo "$@" | cat -v | sed 's/\^/\\C-/' +} + +# +# stty front-end. Parses the argument list and creates two command strings, +# one for stty, another for bind. +# +fstty() +{ + local cmd="" bargs="" + local e + + while [ $# -gt 0 ] + do + case "$1" in + -a) cmd="$cmd everything" + ;; + erase) shift; + e=$(cvt "$1") + cmd="$cmd erase $1" + bargs="$bargs '\"$e\": backward-delete-char'" + ;; + kill) shift + e=$(cvt "$1") + cmd="$cmd kill $1" + bargs="$bargs '\"$e\": unix-line-discard'" + ;; + werase) shift; + e=$(cvt "$1") + cmd="$cmd erase $1" + bargs="$bargs '\"$e\": backward-kill-word'" + ;; + lnext) shift; + e=$(cvt "$1") + cmd="$cmd erase $1" + bargs="$bargs '\"$e\": quoted-insert'" + ;; + *) cmd="$cmd $1" + ;; + esac + shift + done + + command stty $cmd + if [ -n "$bargs" ]; then + builtin bind $bargs + fi +} diff --git a/examples/functions/func b/examples/functions/func new file mode 100644 index 0000000..e7696f7 --- /dev/null +++ b/examples/functions/func @@ -0,0 +1,43 @@ +# +# func -- print out definitions for functions named by arguments +# +# usage: func name [name ...] +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1991 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +func() +{ + local status=0 + + if [ $# -eq 0 ] ; then + echo "usage: func name [name...]" 1>&2 + return 1 + fi + + for f + do + if [ "$(builtin type -type $f)" != "function" ] ; then + echo "func: $f: not a function" 1>&2 + status=1 # one failed + continue + fi + builtin type $f | sed 1d + done + return $status +} diff --git a/examples/functions/inetaddr b/examples/functions/inetaddr new file mode 100644 index 0000000..9e72613 --- /dev/null +++ b/examples/functions/inetaddr @@ -0,0 +1,79 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 2002 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# inet2hex - Internet address conversion, dotted-decimal to hex +# +inet2hex () +{ + local IFS + + IFS=. + set -- $1 + + if (( $# != 4 )); then + echo "inet2hex: incorrect input format: $1" >&2 + echo "inet2hex: usage: inet2hex XX.XX.XX.XX" >&2 + return 2 + fi + + printf "0x%02x%02x%02x%02x\n" $1 $2 $3 $4 +} + +# +# hex2inet - Internet address conversion, hex to dotted-decimal +# +hex2inet () +{ + local x1 x2 x3 x4 + local rev + + OPTIND=1 + while getopts "r" o + do + case "$o" in + r) rev=true;; + *) echo "hex2inet: usage: hex2inet [-r] [0x]XXXXXXXX" >&2 ; exit 2;; + esac + done + shift $(( $OPTIND - 1 )) + + case "$1" in + 0x*) h=${1#??} ;; + *) h=$1 ;; + esac + + if (( ${#h} != 8 )); then + echo "hex2inet: $h not in inet format" >&2 + echo "hex2inet: usage: hex2inet [0x]XXXXXXXX" >&2 + return 2 + fi + + x1=$(( 0x${h:0:2} )) + x2=$(( 0x${h:2:2} )) + x3=$(( 0x${h:4:2} )) + x4=$(( 0x${h:6:2} )) + + if [ -z "$rev" ] ; then + printf "%d.%d.%d.%d\n" $x1 $x2 $x3 $x4 + else + printf "%d.%d.%d.%d\n" $x4 $x3 $x2 $x1 + fi + return 0 +} diff --git a/examples/functions/inpath b/examples/functions/inpath new file mode 100644 index 0000000..cb4c93d --- /dev/null +++ b/examples/functions/inpath @@ -0,0 +1,14 @@ +inpath() +{ + local PROG + path=$(echo $PATH | sed 's/^:/.:/ + s/::/:.:/g + s/:$/:./ + s/:/ /g') + + for x in $path + do + [ -x $x/$1 ] && { PROG=$x/$1; break; } + done + [ -n "$PROG" ] +} diff --git a/examples/functions/isnum2 b/examples/functions/isnum2 new file mode 100644 index 0000000..583e374 --- /dev/null +++ b/examples/functions/isnum2 @@ -0,0 +1,41 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1998 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +isnum2() +{ + case "$1" in + '[-+]' | '') return 1;; # empty or bare `-' or `+' + [-+]*[!0-9]*) return 1;; # non-digit with leading sign + [-+]*) return 0;; # OK + *[!0-9]*) return 1;; # non-digit + *) return 0;; # OK + esac +} + +# this one handles floating point +isnum3() +{ + case "$1" in + '') return 1;; # empty + *[!0-9.+-]*) return 1;; # non-digit, +, -, or . + *?[-+]*) return 1;; # sign as second or later char + *.*.*) return 1;; # multiple decimal points + *) return 0;; # OK + esac +} diff --git a/examples/functions/isvalidip b/examples/functions/isvalidip new file mode 100644 index 0000000..0b2dafe --- /dev/null +++ b/examples/functions/isvalidip @@ -0,0 +1,14 @@ +# Thanks to Chris F. A. Johnson <c.f.a.johnson@rogers.com> for this one +is_validip() +{ + case "$*" in + ""|*[!0-9.]*|*[!0-9]) return 1 ;; + esac + + local IFS=. + set -- $* + + [ $# -eq 4 ] && + [ ${1:-666} -le 255 ] && [ ${2:-666} -le 255 ] && + [ ${3:-666} -le 255 ] && [ ${4:-666} -le 254 ] +} diff --git a/examples/functions/ksh-cd b/examples/functions/ksh-cd new file mode 100644 index 0000000..26b00a9 --- /dev/null +++ b/examples/functions/ksh-cd @@ -0,0 +1,54 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 2001 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# ksh-like `cd': cd [-LP] [dir [change]] +# +cd() +{ + OPTIND=1 + while getopts "LP" opt + do + case $opt in + L|P) CDOPTS="$CDOPTS -$opt" ;; + *) echo "$FUNCNAME: usage: $FUNCNAME [-LP] [dir] [change]" >&2 + return 2;; + esac + done + + shift $(( $OPTIND - 1 )) + + case $# in + 0) builtin cd $CDOPTS "$HOME" ;; + 1) builtin cd $CDOPTS "$@" ;; + 2) old="$1" new="$2" + case "$PWD" in + *$old*) ;; + *) echo "${0##*/}: $FUNCNAME: bad substitution" >&2 ; return 1 ;; + esac + + dir=${PWD//$old/$new} + + builtin cd $CDOPTS "$dir" && echo "$PWD" + + ;; + *) echo "${0##*/}: $FUNCNAME: usage: $FUNCNAME [-LP] [dir] [change]" >&2 + return 2 ;; + esac +} diff --git a/examples/functions/ksh-compat-test b/examples/functions/ksh-compat-test new file mode 100644 index 0000000..919d82b --- /dev/null +++ b/examples/functions/ksh-compat-test @@ -0,0 +1,58 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1999 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# replacements for test/[ that do arithmetic expansion on the operands to +# the arithmetic operators, like ksh. +# +function test() +{ + local -i n1 n3 + case "$#" in + 3) case "$2" in + -lt|-gt|-eq|-ne|-le|-ge) n1=$(( $1 )) + n3=$(( $3 )) + builtin test "$n1" $2 "$n3" + return $?;; + *) builtin test "$@" ;; + esac;; + *) builtin test "$@" ;; + esac +} + +function [() +{ + local -i n1 n3 + case "$#" in + 4) case "$2" in + -lt|-gt|-eq|-ne|-le|-ge) n1=$(( $1 )) + n3=$(( $3 )) + builtin [ "$n1" $2 "$n3" ] + return $?;; + *) builtin [ "$@" ;; + esac;; + *) builtin [ "$@" ;; + esac +} + +q=7 + +[ q -lt 10 ] +echo $? +[ $q -lt 10 ] +echo $? diff --git a/examples/functions/kshenv b/examples/functions/kshenv new file mode 100644 index 0000000..9faba08 --- /dev/null +++ b/examples/functions/kshenv @@ -0,0 +1,246 @@ +# +# .kshenv -- functions and aliases to provide the beginnings of a ksh +# environment for bash. +# +# Chet Ramey +# chet@ins.CWRU.Edu +# + +# +# Copyright 2002 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# These are definitions for the ksh compiled-in `exported aliases'. There +# are others, but we already have substitutes for them: "history", "type", +# and "hash". +# +alias r="fc -s" +alias functions="typeset -f" +alias integer="typeset -i" +alias nohup="nohup " +alias command="command " +alias stop="kill -s STOP" +alias redirect="command exec" +alias hist="fc" + +# +# An almost-ksh compatible `whence' command. This is as hairy as it is +# because of the desire to exactly mimic ksh (whose behavior was determined +# empirically). +# +# This depends somewhat on knowing the format of the output of the bash +# `builtin type' command. +# + +whence() +{ + local vflag pflag fflag defarg c + local path + + vflag= aflag= pflag= fflag= + path= + if [ "$#" = "0" ] ; then + echo "whence: usage: whence [-afpv] name..." >&2 + return 2 + fi + + OPTIND=1 + while getopts "avfp" c + do + case "$c" in + a) defarg=-a ;; + f) fflag=1 ;; # no-op + p) pflag=1 ;; + v) vflag=1 ;; + ?) echo "whence: $1: unknown option" >&2 + echo "whence: usage: whence [-afpv] name..." >&2 + return 2 ;; + esac + done + + shift $(( $OPTIND - 1 )) + + if [ "$#" = "0" ] ; then + echo "whence: usage: whence [-afpv] name..." >&2 + return 2 + fi + + for cmd + do + if [ "$vflag" ] ; then + if [ -z "$defarg" ]; then + builtin type $cmd | sed 1q + else + if builtin type $defarg -t $cmd | grep 'function$' >/dev/null 2>&1; then + # HAIRY awk script to suppress + # printing of function body -- could + # do it with sed, but I don't have + # that kind of time + builtin type $defarg $cmd | awk ' +BEGIN {printit = 1;} +$1 == "'$cmd'" && $2 == "()" {printit=0; next; } +/^}$/ { if (printit == 0) printit=1 ; else print $0; next ; } +/.*/ { if (printit) print $0; }' + else + builtin type $defarg $cmd + fi + fi + else + path=$(builtin type $defarg -p $cmd) + if [ "$path" ] ; then + echo $path + else + case "$cmd" in + /*) echo "" ;; + *) case "$(builtin type -t $cmd)" in + "") echo "" ;; + *) echo "$cmd" ;; + esac + ;; + esac + fi + fi + done + return 0 +} + +# +# For real ksh homeboy fanatics, redefine the `type' builtin with a ksh +# version. +# +#type() +#{ +# whence -v "$*" +#} + +# +# ksh-like `cd': cd [-LP] [dir [change]] +# +cd() +{ + OPTIND=1 + while getopts "LP" opt + do + case $opt in + L|P) CDOPTS="$CDOPTS -$opt" ;; + *) echo "$FUNCNAME: usage: $FUNCNAME [-LP] [dir] [change]" >&2 + return 2;; + esac + done + + shift $(( $OPTIND - 1 )) + + case $# in + 0) builtin cd $CDOPTS "$HOME" ;; + 1) builtin cd $CDOPTS "$@" ;; + 2) old="$1" new="$2" + case "$PWD" in + *$old*) ;; + *) echo "${0##*/}: $FUNCNAME: bad substitution" >&2 ; return 1 ;; + esac + + dir=${PWD//$old/$new} + + builtin cd $CDOPTS "$dir" && echo "$PWD" + + ;; + *) echo "${0##*/}: $FUNCNAME: usage: $FUNCNAME [-LP] [dir] [change]" >&2 + return 2 ;; + esac +} + +# +# ksh print emulation +# +# print [-Rnprsu[n]] [-f format] [arg ...] +# +# - end of options +# -R BSD-style -- only accept -n, no escapes +# -n do not add trailing newline +# -p no-op (no coprocesses) +# -r no escapes +# -s print to the history file +# -u n redirect output to fd n +# -f format printf "$format" "$@" +# + +print() +{ + local eflag=-e + local nflag= fflag= c + local fd=1 + + OPTIND=1 + while getopts "fRnprsu:" c + do + case $c in + R) eflag= ;; + r) eflag= ;; + n) nflag=-n ;; + s) sflag=y ;; + f) fflag=y ;; + u) fd=$OPTARG ;; + p) ;; + esac + done + shift $(( $OPTIND - 1 )) + + if [ -n "$fflag" ]; then + builtin printf "$@" >&$fd + return + fi + + case "$sflag" in + y) builtin history -s "$*" ;; + *) builtin echo $eflag $nflag "$@" >&$fd + esac +} + +# substring function +# this function should be equivalent to the substring built-in which was +# eliminated after the 06/29/84 version +substring () +{ + local lpat flag str #local variables + set -f + case $1 in + -l|-L) + flag=$1 + lpat=$2 + shift 2 + ;; + esac + # test for too few or too many arguments + if [ x"$1" = x ] || [ $# -gt 2 ]; then + print -u2 'substring: bad argument count' + return 1 + fi + str=$1 + if [ x"$flag" = x-l ]; then #substring -l lpat + str=${str#$lpat} + elif [ x"$flag" = x-L ]; then + str=${str##$lpat} #substring -L lpat + fi + + if [ x"$2" != x ]; then + echo ${str%$2} + else + echo $str + fi + + return 0 +} diff --git a/examples/functions/login b/examples/functions/login new file mode 100644 index 0000000..3d59683 --- /dev/null +++ b/examples/functions/login @@ -0,0 +1,11 @@ +# replace the `login' and `newgrp' builtins in old bourne shells + +login() +{ + exec login "$@" +} + +newgrp() +{ + exec newgrp "$@" +} diff --git a/examples/functions/notify.bash b/examples/functions/notify.bash new file mode 100644 index 0000000..ed4377c --- /dev/null +++ b/examples/functions/notify.bash @@ -0,0 +1,77 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1992 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +trap _notify CHLD +NOTIFY_ALL=false +unset NOTIFY_LIST +unalias false + +false() +{ + return 1 +} + +_notify () +{ + local i j + local newlist= + + if $NOTIFY_ALL + then + return # let bash take care of this itself + elif [ -z "$NOTIFY_LIST" ]; then + return + else + set -- $NOTIFY_LIST + for i in "$@" + do + j=$(jobs -n %$i) + if [ -n "$j" ]; then + echo "$j" + jobs -n %$i >/dev/null + else + newlist="newlist $i" + fi + done + NOTIFY_LIST="$newlist" + fi +} + +notify () +{ + local i j + + if [ $# -eq 0 ]; then + NOTIFY_ALL=: + set -b + return + else + for i in "$@" + do + # turn a valid job spec into a job number + j=$(jobs $i) + case "$j" in + [*) j=${j%%]*} + j=${j#[} + NOTIFY_LIST="$NOTIFY_LIST $j" + ;; + esac + done + fi +} diff --git a/examples/functions/seq b/examples/functions/seq new file mode 100644 index 0000000..c1953ee --- /dev/null +++ b/examples/functions/seq @@ -0,0 +1,48 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1995 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Generate a sequence from m to n, m defaults to 1. + +seq () +{ + declare -i lo hi i # makes local + local _SEQ + + case $# in + 1) seq 1 "$1" ; return $? ;; + 2) lo=$1 hi=$2 + i=$lo _SEQ="" + while let "i <= hi"; do + _SEQ="${_SEQ}$i " + let i+=1 + done + echo "${_SEQ# }" + return 0 ;; + *) echo seq: usage: seq [low] high 1>&2 ; return 2 ;; + esac +} + +# like the APL `iota' function (or at least how I remember it :-) +iota() +{ + case $# in + 1) seq 1 "$1"; return $?;; + *) echo "iota: usage: iota high" 1>&2; return 2;; + esac +} diff --git a/examples/functions/seq2 b/examples/functions/seq2 new file mode 100644 index 0000000..4a54498 --- /dev/null +++ b/examples/functions/seq2 @@ -0,0 +1,56 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1998 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Generate a sequence from m to n, m defaults to 1. + +seq () +{ + declare -i lo hi i # makes local + local _SEQ INIT COMPARE STEP + + case "$1" in + -r) INIT='i=$hi _SEQ=""' COMPARE='let "i >= $lo"' STEP='let i-=1' ; shift ;; + *) INIT='i=$lo _SEQ=""' COMPARE='let "i <= $hi"' STEP='let i+=1' ;; + esac + + case $# in + 1) lo=1 hi="$1" ;; + 2) lo=$1 hi=$2 ;; + *) echo seq: usage: seq [-r] [low] high 1>&2 ; return 2 ;; + esac + + # equivalent to the as-yet-unimplemented + # for (( "$INIT" ; "$COMPARE" ; "$STEP" )); do _SEQ="${_SEQ}$i "; done + eval "$INIT" + while eval "$COMPARE"; do + _SEQ="${_SEQ}$i " + eval "$STEP" + done + echo "${_SEQ# }" + return 0 +} + +# like the APL `iota' function (or at least how I remember it :-) +iota() +{ + case $# in + 1) seq 1 "$1"; return $?;; + *) echo "iota: usage: iota high" 1>&2; return 2;; + esac +} diff --git a/examples/functions/shcat b/examples/functions/shcat new file mode 100644 index 0000000..c5d3d63 --- /dev/null +++ b/examples/functions/shcat @@ -0,0 +1,7 @@ +shcat() +{ + while read -r line + do + echo "$line" + done +} diff --git a/examples/functions/shcat2 b/examples/functions/shcat2 new file mode 100644 index 0000000..6fe90f4 --- /dev/null +++ b/examples/functions/shcat2 @@ -0,0 +1,19 @@ +shcat() +{ + while read -r line + do + echo "$line" + done +} + +shcat2() +{ + while [ $# -ge 1 ]; do + case "$1" in + -) shcat ;; + *) shcat < "$1" ;; + esac + shift + done + exit 0 +} diff --git a/examples/functions/sort-pos-params b/examples/functions/sort-pos-params new file mode 100644 index 0000000..3fbf7db --- /dev/null +++ b/examples/functions/sort-pos-params @@ -0,0 +1,69 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 2001 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Sort the positional paramters. +# Make sure the positional parameters are passed as arguments to the function. +# If -u is the first arg, remove duplicate array members. +sort_posparams() +{ + local -a R + local u + + case "$1" in + -u) u=-u ; shift ;; + esac + + # if you want the case of no positional parameters to return success, + # remove the error message and return 0 + if [ $# -eq 0 ]; then + echo "$FUNCNAME: argument expected" >&2 + return 1 + fi + + # make R a copy of the positional parameters + R=( "${@}" ) + + # sort R. + R=( $( printf "%s\n" "${R[@]}" | sort $u) ) + + printf "%s\n" "${R[@]}" + return 0 +} + +# will print everything on separate lines +set -- 3 1 4 1 5 9 2 6 5 3 2 +sort_posparams "$@" + +# sets without preserving quoted parameters +set -- $( sort_posparams "$@" ) +echo "$@" +echo $# + +# sets preserving quoted parameters, beware pos params with embedded newlines +set -- 'a b' 'a c' 'x z' + +oifs=$IFS +IFS=$'\n' +set -- $( sort_posparams "$@" ) +IFS="$oifs" + +echo "$@" +echo $# + +sort_posparams diff --git a/examples/functions/substr b/examples/functions/substr new file mode 100644 index 0000000..f19f5d7 --- /dev/null +++ b/examples/functions/substr @@ -0,0 +1,97 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 2002 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# substr -- a function to emulate the ancient ksh builtin +# + +# +# -l == shortest from left +# -L == longest from left +# -r == shortest from right (the default) +# -R == longest from right + +substr() +{ + local flag pat str + local usage="usage: substr -lLrR pat string or substr string pat" + + case "$1" in + -l | -L | -r | -R) + flag="$1" + pat="$2" + shift 2 + ;; + -*) + echo "substr: unknown option: $1" + echo "$usage" + return 1 + ;; + *) + flag="-r" + pat="$2" + ;; + esac + + if [ "$#" -eq 0 ] || [ "$#" -gt 2 ] ; then + echo "substr: bad argument count" + return 2 + fi + + str="$1" + + # + # We don't want -f, but we don't want to turn it back on if + # we didn't have it already + # + case "$-" in + "*f*") + ;; + *) + fng=1 + set -f + ;; + esac + + case "$flag" in + -l) + str="${str#$pat}" # substr -l pat string + ;; + -L) + str="${str##$pat}" # substr -L pat string + ;; + -r) + str="${str%$pat}" # substr -r pat string + ;; + -R) + str="${str%%$pat}" # substr -R pat string + ;; + *) + str="${str%$2}" # substr string pat + ;; + esac + + echo "$str" + + # + # If we had file name generation when we started, re-enable it + # + if [ "$fng" = "1" ] ; then + set +f + fi +} diff --git a/examples/functions/substr2 b/examples/functions/substr2 new file mode 100644 index 0000000..fc21b98 --- /dev/null +++ b/examples/functions/substr2 @@ -0,0 +1,99 @@ +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 2002 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# substr -- a function to emulate the ancient ksh builtin +# + +# -l == remove shortest from left +# -L == remove longest from left +# -r == remove shortest from right (the default) +# -R == remove longest from right + +substr() +{ + local flag pat str + local usage="usage: substr -lLrR pat string or substr string pat" + local options="l:L:r:R:" + + OPTIND=1 + while getopts "$options" c + do + case "$c" in + l | L | r | R) + flag="-$c" + pat="$OPTARG" + ;; + '?') + echo "$usage" + return 1 + ;; + esac + done + + if [ "$OPTIND" -gt 1 ] ; then + shift $(( $OPTIND -1 )) + fi + + if [ "$#" -eq 0 ] || [ "$#" -gt 2 ] ; then + echo "substr: bad argument count" + return 2 + fi + + str="$1" + + # + # We don't want -f, but we don't want to turn it back on if + # we didn't have it already + # + case "$-" in + "*f*") + ;; + *) + fng=1 + set -f + ;; + esac + + case "$flag" in + -l) + str="${str#$pat}" # substr -l pat string + ;; + -L) + str="${str##$pat}" # substr -L pat string + ;; + -r) + str="${str%$pat}" # substr -r pat string + ;; + -R) + str="${str%%$pat}" # substr -R pat string + ;; + *) + str="${str%$2}" # substr string pat + ;; + esac + + echo "$str" + + # + # If we had file name generation when we started, re-enable it + # + if [ "$fng" = "1" ] ; then + set +f + fi +} diff --git a/examples/functions/whatis b/examples/functions/whatis new file mode 100644 index 0000000..570c585 --- /dev/null +++ b/examples/functions/whatis @@ -0,0 +1,71 @@ +# +# whatis -- and implementation of the 10th Edition Unix sh builtin `whatis' +# command. +# +# usage: whatis arg [...] +# +# For each argument, whatis prints the associated value as a parameter, +# builtin, function, alias, or executable file as appropriate. In each +# case, the value is printed in a form which would yield the same value +# if typed as input to the shell itself. +# +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1994 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +whatis() +{ + local wusage='usage: whatis arg [arg...]' + local fail=0 + + if [ $# -eq 0 ] ; then + echo "$wusage" + return 1 + fi + + for arg + do + case $(builtin type -type $arg 2>/dev/null) in + "alias") + builtin alias "$arg" + ;; + "function") + builtin type "$arg" | sed 1d + ;; + "builtin") + echo builtin "$arg" + ;; + "file") + builtin type -path "$arg" + ;; + *) + # OK, we could have a variable, or we could have nada + if [ "$(eval echo \${$arg+set})" = "set" ] ; then + # It is a variable, and it is set + echo -n "$arg=" + eval echo '\"'\$$arg'\"' + else + echo whatis: $arg: not found + fail=1 + fi + ;; + esac + done + return $fail +} diff --git a/examples/functions/whence b/examples/functions/whence new file mode 100644 index 0000000..ba27b00 --- /dev/null +++ b/examples/functions/whence @@ -0,0 +1,78 @@ +# +# An almost-ksh compatible `whence' command. This is as hairy as it is +# because of the desire to exactly mimic ksh. +# +# This depends somewhat on knowing the format of the output of the bash +# `builtin type' command. +# +# Chet Ramey +# chet@ins.CWRU.Edu +# +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1994 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +whence() +{ + local vflag= path= + + if [ "$#" = "0" ] ; then + echo "whence: argument expected" + return 1 + fi + case "$1" in + -v) vflag=1 + shift 1 + ;; + -*) echo "whence: bad option: $1" + return 1 + ;; + *) ;; + esac + + if [ "$#" = "0" ] ; then + echo "whence: bad argument count" + return 1 + fi + + for cmd + do + if [ "$vflag" ] ; then + echo $(builtin type $cmd | sed 1q) + else + path=$(builtin type -path $cmd) + if [ "$path" ] ; then + echo $path + else + case "$cmd" in + /*) if [ -x "$cmd" ]; then + echo "$cmd" + fi + ;; + *) case "$(builtin type -type $cmd)" in + "") ;; + *) echo "$cmd" + ;; + esac + ;; + esac + fi + fi + done + return 0 +} diff --git a/examples/functions/which b/examples/functions/which new file mode 100644 index 0000000..f0db290 --- /dev/null +++ b/examples/functions/which @@ -0,0 +1,62 @@ +# +# which - emulation of `which' as it appears in FreeBSD +# +# usage: which [-as] command [command...] +# +# +# Chet Ramey <chet.ramey@case.edu> +# +# Copyright 1999 Chester Ramey +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# TThis program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +which() +{ + local aflag sflag ES a opt + + OPTIND=1 + while builtin getopts as opt ; do + case "$opt" in + a) aflag=-a ;; + s) sflag=1 ;; + ?) echo "which: usage: which [-as] command [command ...]" >&2 + exit 2 ;; + esac + done + + (( $OPTIND > 1 )) && shift $(( $OPTIND - 1 )) + + # without command arguments, exit with status 1 + ES=1 + + # exit status is 0 if all commands are found, 1 if any are not found + for command; do + # if $command is a function, make sure we add -a so type + # will look in $PATH after finding the function + a=$aflag + case "$(builtin type -t $command)" in + "function") a=-a;; + esac + + if [ -n "$sflag" ]; then + builtin type -p $a $command >/dev/null 2>&1 + else + builtin type -p $a $command + fi + ES=$? + done + + return $ES +} |