Re: BASH help please??

From: Chris F.A. Johnson (cfajohnson_at_gmail.com)
Date: 06/29/05


Date: Wed, 29 Jun 2005 08:47:50 -0400

On 2005-06-29, HellZFury@gmail.com wrote:
> I just have a few questions... I've looked in the "Advanced
> Bash-Scripting Guide" as someone else had suggested. It has answered a
> lot of my questions. However, I still have a few that I can't seem to
> find the answer to. Anyone that could help me/answer a few of these
> questions would have my gratitude.
> Also, since this obviously isn't the right place to be asking these
> questions, can anyone either give me a link to a more appropriate
> listserv or (if they are BASH mavericks) offer to help me offlist??
> This way I don't have to bother everyone.

   This is as good a place as any. Other people can learn from the
   responses you get.

> 1. How can I get find -exec to operate properly on "{" inside of a
> replacement "thingy"? Example:
> find -d ./* -exec echo "{}" ${{}//"e"/"h"} \;

   Put the commands you want executed in a separate script, e.g.:

#!/bin/sh
eval echo "\$1 \${$1//e/h}"

   And call it with the -exec operand:

find -d ./* -exec /path/to/script {} \;

> 2. I believe the following is interpreting "09" to be a ASCII
> reference (not sure though). What would be a good workaround for
> this?:
> user@host:~$ file_prompt.sh ~/test/
> 1:example_file1 7:example_file15 13:example_file6
> 2:example_file10 8:example_file16 14:example_file7
> 3:example_file11 9:example_file2 15:example_file8
> 4:example_file12 10:example_file3 16:example_file9
> 5:example_file13 11:example_file4
> 6:example_file14 12:example_file5
> Choose: 09/Users/matt/my_bin/auxiliary_scripts/file_prompt.sh: line
> 64: 09: value too great for base (error token is "09")
> /Users/matt/my_bin/auxiliary_scripts/file_prompt.sh: line 1: 09: value
> too great for base (error token is "09")

    The message tells you that 09 is too great for the base; numbers
    beginning with 0 are interpreted as octal. If a number may have a
    leading zero, remove it before using it in any numerical
    expression:

echo $(( ${x#0} + 1 ))

> 3. What would be the best ways to implement a "timeout" period in read
> statements? I am aware of the parameter in read, but that simply exits
> without returning a value. If the user types a 1, for example, and
> then waits a second or so, I want it to automatically accept the 1.

     If you use the timeout (-t N) option, it will return 1 if it times
     out.

     Perhaps you want instead to accept only a single character; for
     that use the -n option:

read -n1 var

> 4. What would be the best way to protect against user input of special
> characters such as "tabs", letters, and symbols?

     What do you mean by "protect"?

     If you want to keep leading whitespace, set IFS to an empty
     string:

IFS= read var

     If you also want raw input (no interpreting of backslashes as
     special), use -r:

IFS= read -r var

> 5. Can someone please just look at my code and answer the following 2
> questions?

     If you want your code to be read, do not post via Google groups,
     as the code gets mangled, and it is very time consuming to figure
     out exactly what it is supposed to be. (Or else make sure all
     your lines are short enough not to be wrapped.)

     Second, post the question with the code it refers to.

     Third, describe what the script is supposed to do, and give a
     detailed description of its failure, including the exact inputs
     you give it and any error messages it printed (cut and paste, do
     not retype the messages).

> a. How come the following is happening?
> user@host:~$ Script_template.sh test "foo bar"
> center.sh: String length greater than width!

     Since you coded the condition that causes the error message, you
     know where the error occurred, and you can easily examine the
     values at that point.

> b. Is there a better way to take in input from commands than "$(<
> /dev/stdin)"?

     From the code, it's not clear what you are trying to do. The
     command already has its stdin connected to a pipe.

> =============================================================================
> file_prompt.sh
> -----------------------------------------------------------------------------
> #!/bin/bash
> ARGS_min=1 # $work_dir
> ARGS_max=2 # $work_dir $prompt
> E_FILE_DOES_NOT_EXIST=85
> E_PERM_DENIED=87
> E_FILE_NOT_DIR=88
> E_NUM_ARGS=95
> E_INVALID_USER_INPUT=98
> E_USER_ABORT=105
> work_dir="$1" # directory to work in
> prompt="${2-"Choose"}" # prompt to show user
> #chosen_num # Num from prompt
> #chosen_file # Name of chosen file
>
> max_tries=3 # Max num of prompts if invalid file
> selected
>
> command ls "$work_dir" | grep -n -v 'randtonotmatch' | column >&2
> num_files=$(command ls $work_dir | grep -c -v 'randtonotmatch')
> digits=1
> while (( $num_files >= 10 ))

    The portable forms are perfectly usable; you gain nothing by using
    ((...)) instead of:

while [ $num_files -ge 10 ]

> do
> ((digits += 1))
> ((num_files /= 10))

    Use the portable (POSIX) form of arithmetic:

digits=$(( $digits + 1 ))
num_files=$(( $num_files / 10 ))

    You could also use the following and still be POSIX conformant,
    but you don't gain anything, and there are some otherwise POSIX
    shells that do not support it:

: $((digits += 1)) $((num_files /= 10))

> done
> num_files=$(command ls "$work_dir" | grep -c -v 'randtonotmatch')
> tries_left=$((max_tries - 1))
> read -p "$prompt: " -n $digits chosen_num >&2
> while [[ ! $num_files -ge $((chosen_num)) && $chosen_num && $tries_left
> -ge 1 ]]

     I'm not sure exactly what the logic is supposed to be, but it
     should be not far from this:

while [ $num_files -lt $chosen_num ] &&
      [ "$chosen_num" ] && [ $tries_left -ge 1 ]

> do
> echo -e "\nInvalid file selection! You have $tries_left tries
> left." >&2
> read -p "$prompt: " -n $digits chosen_num >&2
> ((tries_left -= 1))

tries_left=$(( $tries_left - 1))

> done
> if [[ $chosen_num -eq 0 && $chosen_num ]]

    What exactly are you testing here?

> then
> echo -en "\n" >&2
> fi
> if [[ ! $chosen_num || $chosen_num -eq 0 ]]
> then
> echo "$(basename $0): ABORT!!!" >&2

    There is no need for the basename command in any POSIX shell:

echo "${0##*/}: ABORT!!!" >&2

> exit $E_USER_ABORT
> fi
> chosen_file=$(command ls "$work_dir" | grep -n -v 'randtonotmatch' |
> grep ^$((chosen_num)): | cut -d ":" -f 2-)

    Why $((chosen_num)) instead of $chosen_num?

> if [[ $tries_left -eq 0 && ! -e "$chosen_file" ]]
> then
> echo >&2
> echo -e "\n$(basename $0): You have repeatedly chosen an invalid
> file! This program will now quit." >&2
> exit $E_INVALID_USER_INPUT
> fi
> echo " - $chosen_file" >&2
> echo "$chosen_file" >&1
> =============================================================================
> script_template.sh (3)
> -----------------------------------------------------------------------------
> #!/bin/bash
> filename="$1" # filename for script
> description="$2" # script description
> bw=50 # width of header box
>
> #echo "$(center.sh $(split_string.sh "$description" 5) $bw '#' '#')" >>
> $filename
> echo "$(split_string.sh "$description" 5 | center.sh $(< /dev/stdin)
> $bw '#' '#')" >> $filename
> =============================================================================
> center.sh (3)
> -----------------------------------------------------------------------------
> #!/bin/bash
> E_WRONG_INPUT=97
> text="$1"
> width=$2
> prefix="$3"
> suffix="$4"
> whitespace=$((width-${#text}))
>
> if ((whitespace < 0))
> then
> echo "$(basename $0): String length greater than width!" > /dev/stderr

    No need for basename. See above.

    The usual syntax for sending to stderr is:

echo "....." >&2

> exit $E_WRONG_INPUT
> fi
> odd_offset=$((whitespace % 2))

     That's the right way to do arithmetic.

> before="$(string_mult.sh ' ' $((whitespace / 2 - ${#prefix})) )"
> after="$(string_mult.sh ' ' $((whitespace / 2 + odd_offset -
> ${#suffix})) )"
> echo "$prefix$before$text$after$suffix"

   It would be much easier with printf.

   I use this function to centre a string:

centre() { ## centre string on N columns. USAGE: centre [-N] string [...]
    case $1 in
         -[0-9]*) c_cols=${1#-}
                  shift
                  ;;
         *) c_cols=${COLUMNS:-78} ;; ## Default width
    esac
    string="$*"
    c_len=$(( ( $c_cols - ${#string} ) / 2 + ${#string}))
    printf "%${c_len}.${c_len}s" "$*"
}

> =============================================================================
> string_mult.sh (3)
> -----------------------------------------------------------------------------
> #!/bin/bash
> text="$1"
> mult=$2
> ans=""
>
> while [ $mult -gt 0 ]
> do
> ans="$ans$text"
> mult=$((mult - 1))
> done
> echo "$ans"
> =============================================================================
> split_string.sh (3)
> -----------------------------------------------------------------------------
> #!/bin/bash
> string="$1"
> max_per_section=$2
>
> for word in "$string"

     You are only giving the loop a single argument; presumably you
     mean:

for word in $string

> do
> if (($((${#word} + ${#this_line})) < $max_per_section))
> then
> this_line="$this_line $word"
> else
> echo -n "$this_line" >&1

    There's no need for >&1.

> this_line="$word"
> fi
> done
> echo "$this_line"

     Consider using "fold -s" instead of a script; it may be faster (I
     wrote something similar just yesterday, and found that the fold
     command was as fast or faster).

> =============================================================================

     I haven't gone over the entire logic of your script, but you
     might do well to consider a loop like this:

while :
do
   : do_something
   if test something
   then
      break
   fi
done

     Some of the scripts would be better as functions, so that you can
     avoid command substitution and forking new processes which can
     slow down your script.

-- 
    Chris F.A. Johnson                     <http://cfaj.freeshell.org>
    ==================================================================
    Shell Scripting Recipes: A Problem-Solution Approach, 2005, Apress
    <http://www.torfree.net/~chris/books/cfaj/ssr.html>


Relevant Pages

  • Re: how to take each line from file as an input to a command
    ... then check for a string "is_valid" in a file with extension .per ... command is not going to be a number, so it can never -eq 0. ... echo "No Match found" ... Shell Scripting Recipes: ...
    (comp.unix.shell)
  • Re: Possible bug in gosub vars
    ... | gosub test %var ... Before I could use the above batch program, I had to insert a QUIT command ... For the case of set var=`test string` the behavior is 100% correct: ...
    (comp.os.msdos.4dos)
  • Re: :t and getting senile.
    ... how do you strip off the end of string such as a command line string so ... Shell Scripting Recipes: ...
    (comp.unix.shell)
  • Re: How to do multiple find/replace?
    ... > contain a string of text that I need to replace with another string of ... Is there a way to do this from the command line, ... Shell Scripting Recipes: | My code in this post, if any, ... Prev by Date: ...
    (comp.unix.shell)
  • Re: Bash : insert to the start of the file?
    ... > what is the command to insert a string to the top? ... If you find it awkward, put it in a function or script, then you ... Shell Scripting Recipes: ...
    (comp.unix.shell)