Having difficulty writing a script to sort words - bash

I am dealing with sorting words in Bash according to a given argument. I am given either argument -r, -a , -v or -h and according to it there are options to sort the words, as you can see at my "help".
Somehow, if I pass the argument -r it creates an error. I really don't understand what I am doing wrong, as if[["$arg"=="-a"]] works, but I have to use case somehow.
Here is my code:
#!/bin/bash
# Natalie Zubkova , zubkonat
# zubkonat#cvut.fel.cz , LS
#help
help="This script will calculate occurances of words in a given file, and it will sort them according to the given argument in following order> \n
without parametre = increasing order according to a number of occurance\n
-r = decreasing order according to a number of occurance\n
-a = in alphabetical increasing order\n
-a -r = in alphabetical decreasing order\n
There are also special cases of the given parametre, when the script is not sorting but:\n
-h = for obtaining help \n
-v = for obtaining a number of this task "
# this function will divide a given chain into a words, so we can start calculating the occurances, we also convert all the capital letters to the small ones by - tr
a=0;
r=0;
EXT=0;
if [ "$1" == "-h" ]; then
echo $help
exit 0
fi
if [ "$2" == "-h" ]; then
echo $help
exit 0
fi
if [ "$1" == "-v" ]; then
echo "5"
exit 0
fi
if [ "$2" == "-v" ]; then
echo "5"
exit 0
fi
function swap {
while read x y; do
echo "$y" "$x";
done
}
function clearAll {
sed -e 's/[^a-z]/\n/gI' | tr '[A-Z]' '[a-z]' | sort | uniq -c |awk '{i++; if(i!=1) print $2" "$1}' #swap
}
for arg do
case "$arg" in
"-a")
a=1
;;
"-r")
r=1
;;
"-v")
echo "5" #number of task is 5
exit 0
;;
"-h")
echo $help
exit 0
;;
"-?")
echo "invalid parametre, please display a help using argument h"
exit 0
;;
esac
done
#Sort according to parametres -a and -r
function sortWords {
if [[ a -eq 1 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk1
fi
fi
if [[ a -eq 1 ]]; then
if [[ r -eq 1 ]]; then
clearAll | sort -nk1 -r
fi
fi
if [[ r -eq 1 ]]; then
if [[ a -eq 0 ]]; then
clearAll | sort -nk2 -r
fi
fi
if [[ a -eq 0 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk2
fi
fi
}
#code is from Stackoverflow.com
function cat-all {
while IFS= read -r file
do
if [[ ! -z "$file" ]]; then
cat "$file"
fi
done
}
#histogram
hist=""
for arg do
if [[ ! -e "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -f "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -r "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
fi
done
for arg do
hist="$hist""$arg""\n"
done
echo -e "$hist" | cat-all | sortWords
exit $EXT;
Here is what our upload system which does some test to see if our program works says:
Test #6
> b5.sh -r ./easy.txt
ERROR: script output is wrong:
--- expected output
+++ script stdout
## --- line 1 (167 lines) ; +++ no lines ##
-the 89
-steam 46
-a 39
-of 37
-to 35
...
script written 484 lines, while 484 lines are expected
script error output:
A FILE DOESNT EXIST
cat: invalid option -- 'r'
Try `cat --help' for more information.
script exit value: 1
ERROR: Interrupted due to failed test
If anyone could help me I would really appreciate it.

You forgot to move the parameter index position with shift:
"-r")
r=1
shift
;;
shift above moves to the next command line arg: ./easy.txt in your case.
Without it, read -r file will read -r instead of the file name.

Related

Strange syntax error in if condition - Shell Script

So I'm making a shell script in Ubuntu. It's purpose is simple. You give a command with arguments and you get a different operation each time. The problem is that when I run the the script it won't actually run because of a syntax error in one elif. The most suspicious thing is that I have a similar elif above wich works or at least doesn't pop a syntax error...
I'm leaving my code for you to see it and understand. Thanks in advance!
if [ "$1" = "-a" -a $# -lt 3 ]
then
echo "Add a new line in katalogos!"
read -p "Give me a name... " name
read -p "Give me a surname... " surname
read -p "Give me a city name... " cityName
read -p "Give me a phone number... " num
echo "$name $surname $cityName $num" > katalogos
elif [ "$1" = "-l" -a $# -lt 3 ]
then
echo "Content of katalogos will be sorted numerically and blank lines will be excluded!"
sort -b -n katalogos
elif [ "$1" = "-s" -a $# -lt 4 ]
if [[ $2 != *[!0-9]* ]]
then
echo "Content of katalogos will be sorted according to the second argument!"
sort +$3 katalogos
fi
elif [ "$1" = "-c" -a $# -lt 4 ] // syntax error
if [[ $2 = *[!0-9]* ]]
then
echo "Content of katalogos will be sorted according to the keyword!"
if [ $(grep -e "$2" katalogos | wc -l) -eq 0 ]
then
echo "String is not matched."
else
grep -e "$2" katalogos
fi
fi
elif [ "$1" = "-d" -a ( "$3" = "-b" -o "$3" = "-r" ) ]
if [[ $2 = *[!0-9]* ]]
then
echo "Katalogos's string matching lines will be deleted and blank lines will be in their place, assuming that the third argument equals -b, else just the lines will be deleted!"
if [ $(grep -e $2 katalogos | wc -l) -eq 0 ]
then
echo "String is not matched."
else
if [ "$3" = "-b" ]
then
sed -i "$3" katalogos | sed -i '$ a '
echo "A blank line inserted in place of the deleted one."
else
sed -i "$3" katalogos
echo "Line deleted."
fi
fi
fi
elif [ "$1" = "-n" ]
echo "katalogos's number of blank lines will be shown with the ability to delete them!"
grep -cvP '\S' katalogos
read -p "Do you want to delete them? Type 1 for yes or 0 for no... " ans
if [ $ans -eq 1 ]
then
grep -cvP '\S' file | sed -i
echo "Lines deleted."
fi
else
echo "Help centre!"
echo "-Type ./telcat -a to insert a new line to katalogos."
echo "-Type ./telcat -l to see the contents of katalogos sorted numerically (excluding blank lines)."
echo "-Type ./telcat -s plus a number to see the contents of katalogos sorted by the data that the number points to."
echo "-Type ./telcat -c plus a keyword to see only the lines that match with the word given."
echo "-Type ./telcat -d plus a keyword and -b or -r to delete the lines that contain the word given. Specifically if the third argument is -b it will automatically add a blank line to the deleted one and if it is -r it will not."
echo "-Type ./telcat -n to see the number of blank lines of katalogos."
echo "End of help centre!"
fi

Need alternative to readarray/mapfile for script on older version of Bash

The script is:
#!/bin/bash
# Dynamic Menu Function
createmenu () {
select selected_option; do # in "$#" is the default
if [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#)) ]; then
break;
else
echo "Please make a vaild selection (1-$#)."
fi
done
}
declare -a drives=();
# Load Menu by Line of Returned Command
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
# Display Menu and Prompt for Input
echo "Available Drives (Please select one):";
createmenu "${drives[#]}"
# Split Selected Option into Array and Display
drive=($(echo "${selected_option}"));
echo "Drive Id: ${drive[0]}";
echo "Serial Number: ${drive[1]}";
The older system doesn't have mapfile or readarray so I need to convert that line to some alternative that can read each line of the lsblk output into an array.
The line in question that creates the array is:
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
You can loop over your input and append to the array:
$ while IFS= read -r line; do arr+=("$line"); done < <(printf '%d\n' {0..5})
$ declare -p arr
declare -a arr='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
Or, for your specific case:
while IFS= read -r line; do
drives+=("$line")
done < <(lsblk --nodeps -o name,serial,size | grep "sd")
See the BashFAQ/001 for an excellent explanation why IFS= read -r is a good idea: it makes sure that whitespace is conserved and backslash sequences not interpreted.
Here's the solution I came up with a while back. This is better because it provides a substitute function for older versions of Bash that don't support mapfile/readarray.
if ! type -t readarray >/dev/null; then
# Very minimal readarray implementation using read. Does NOT work with lines that contain double-quotes due to eval()
readarray() {
local cmd opt t v=MAPFILE
while [ -n "$1" ]; do
case "$1" in
-h|--help) echo "minimal substitute readarray for older bash"; exit; ;;
-r) shift; opt="$opt -r"; ;;
-t) shift; t=1; ;;
-u)
shift;
if [ -n "$1" ]; then
opt="$opt -u $1";
shift
fi
;;
*)
if [[ "$1" =~ ^[A-Za-z_]+$ ]]; then
v="$1"
shift
else
echo -en "${C_BOLD}${C_RED}Error: ${C_RESET}Unknown option: '$1'\n" 1>&2
exit
fi
;;
esac
done
cmd="read $opt"
eval "$v=()"
while IFS= eval "$cmd line"; do
line=$(echo "$line" | sed -e "s#\([\"\`]\)#\\\\\1#g" )
eval "${v}+=(\"$line\")"
done
}
fi
You don't have to change your code one bit. It just works!
readarray -t services -u < <(lsblk --nodeps -o name,serial,size | grep "sd")
For those playing along at home, this one aims to provide a mapfile that's feature-compliant with Bash 5, but still runs as far back as Bash 3.x:
#!/usr/bin/env bash
if ! (enable | grep -q 'enable mapfile'); then
function mapfile() {
local DELIM="${DELIM-$'\n'}"; opt_d() { DELIM="$1"; }
local COUNT="${COUNT-"0"}"; opt_n() { COUNT="$1"; }
local ORIGIN="${ORIGIN-"0"}"; opt_O() { ORIGIN="$1"; }
local SKIP="${SKIP-"0"}"; opt_s() { SKIP="$1"; }
local STRIP="${STRIP-"0"}"; opt_t() { STRIP=1; }
local FROM_FD="${FROM_FD-"0"}"; opt_u() { FROM_FD="$1"; }
local CALLBACK="${CALLBACK-}"; opt_C() { CALLBACK="$1"; }
local QUANTUM="${QUANTUM-"5000"}"; opt_c() { QUANTUM="$1"; }
unset OPTIND; local extra_args=()
while getopts ":d:n:O:s:tu:C:c:" opt; do
case "$opt" in
:) echo "${FUNCNAME[0]}: option '-$OPTARG' requires an argument" >&2; exit 1 ;;
\?) echo "${FUNCNAME[0]}: ignoring unknown argument '-$OPTARG'" >&2 ;;
?) "opt_${opt}" "$OPTARG" ;;
esac
done
shift "$((OPTIND - 1))"; set -- ${extra_args[#]+"${extra_args[#]}"} "$#"
local var="${1:-MAPFILE}"
### Bash 3.x doesn't have `declare -g` for "global" scope...
eval "$(printf "%q" "$var")=()" 2>/dev/null || { echo "${FUNCNAME[0]}: '$var': not a valid identifier" >&2; exit 1; }
local __skip="${SKIP:-0}" __counter="${ORIGIN:-0}" __count="${COUNT:-0}" __read="0"
### `while read; do...` has trouble when there's no final newline,
### and we use `$REPLY` rather than providing a variable to preserve
### leading/trailing whitespace...
while true; do
if read -d "$DELIM" -r <&"$FROM_FD"
then [[ ${STRIP:-0} -ge 1 ]] || REPLY="$REPLY$DELIM"
elif [[ -z $REPLY ]]; then break
fi
(( __skip-- <= 0 )) || continue
(( COUNT <= 0 || __count-- > 0 )) || break
### Yes, eval'ing untrusted content is insecure, but `mapfile` allows it...
if [[ -n $CALLBACK ]] && (( QUANTUM > 0 && ++__read % QUANTUM == 0 ))
then eval "$CALLBACK $__counter $(printf "%q" "$REPLY")"; fi
### Bash 3.x doesn't allow `printf -v foo[0]`...
### and `read -r foo[0]` mucks with whitespace
eval "${var}[$((__counter++))]=$(printf "%q" "$REPLY")"
done
}
### Alias `readarray` as well...
readarray() { mapfile "$#"; }
fi
if [[ -z ${PS1+YES} ]]; then
echo "'mapfile' should only be called as a shell function; try \"source ${BASH_SOURCE[0]##*/}\" first..." >&2
exit 1
fi

bash- reading file from stdin and arguments

So I have googled this and thought I found the answers, but it still doesnt work for me.
The program computes the average and median of rows and columns in a file of numbers...
Using the file name works:
./stats -columns test_file
Using cat does not work
cat test_file | ./stats -columns
I am not sure why it doesnt work
#file name was given
if [[ $# -eq 2 ]]
then
fileName=$2
#file name was not given
elif [[ $# -eq 1 ]]
then
#file name comes from the user
fileName=/dev/stdin
#incorrect number of arguments
else
echo "Usage: stats {-rows|-cols} [file]" 1>&2
exit 1
fi
A very simple program that accepts piped input:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
stdin
Test is as follows:
echo "This is piped output" | stdin
To put that into a script / utility similar to the one in the question you might do this:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
rowbool=0
colbool=0
for i in $#
do case "$i" in
-rows) echo "rows set"
rowbool=1
shift
;;
-cols) echo "cols set"
colbool=1
shift
;;
esac
done
if [[ $# -gt 0 ]]
then
fileName=$1
fi
if [[ $# -eq 0 ]]
then fileName=$(stdin)
fi
echo "$fileName"

Getting command line argument that stores in a variable

I am writing a bash script to finger the first three line of user's info.
ex:
$ ./c.sh bob unknown
Login: bob Name: Bob
Directory: /u1/h7/bob Shell: /bin/tcsh
Office: AA 044, x8361 Home Phone: 000-000-0000
unknown: no such user.
Here is my code so far
#!/bin/bash
if [ $# == 0 ]; then
echo "Usage: ./c.sh Login/Username"
exit
else
i=$#
j=1
while [ "$j" -le "$i" ]; do
finger ${$j} | head -n+3
echo
j=$(($j+1))
done
fi
instead of giving what user types for the command line arguments, ${$j} is giving me the the value of $j, any suggestion and help for how to get the login/username? I've tried $($j), $((j)), ${$j}....
The easy answer: stop using unnecessary indirection:
#!/bin/bash
if (( $# == 0 )); then
echo "Usage: ./c.sh Login/Username"
exit
else
while [[ $1 ]]; do
finger "$1" | head -n+3
echo
shift
done
fi
or…
…
for user; do # equivalent to `for user in "$#"; do`
finger "$user" | head -n+3
…
done
You could write it this way:
i=$#
j=1
while [ $j -le $i ]; do
finger "${#:j++:1}" | head -n+3
echo
done
…but you don't need to work that hard.
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "Usage: $0 Login/Username"
exit
else
for ARG in "$#"; do
finger "$ARG" | head -n 3
echo # If you want a newline
done
fi
As simple as it can be.

need a shell script to convert big endian to little endian

I need a shell script program to print the hexadecimal number from big endian to little endian
For example
Input: my virtual address = 00d66d7e
Output: 7e6dd600
How can I can I create this in a bash script?
Just had to do this... but from decimal to little endian.. adapting that here:
echo 00d66d7e | tac -rs .. | echo "$(tr -d '\n')"
achieves the desired result, for arbitrarily sized hexadecimal representations of unsigned integers.
(h/t 'tac -rs' MestreLion, very nice!)
For 32 bit addresses, assuming it's zero padded:
v=00d66d7e
echo ${v:6:2}${v:4:2}${v:2:2}${v:0:2}
# 7e6dd600
Based on Karoly's answer you could use the following script, reading an argument or piped input:
#!/bin/bash
# check 1st arg or stdin
if [ $# -ne 1 ]; then
if [ -t 0 ]; then
exit
else
v=`cat /dev/stdin`
fi
else
v=$1
fi
i=${#v}
while [ $i -gt 0 ]
do
i=$[$i-2]
echo -n ${v:$i:2}
done
echo
For e.g. you could save this script as endian.sh and make it executable with:
chmod u+x endian.sh
Then:
echo 00d66d7e | ./endian.sh
gives you:
7e6dd600
For a different length string:
echo d76f411475428afc90947ee320 | ./endian.sh
result would be:
20e37e9490fc8a427514416fd7
#Update: Modified the script to accept the input either as an argument or from stdin, addressing Freewind's request. So now:
./endian.sh d76f411475428afc90947ee320
also works and gives you:
20e37e9490fc8a427514416fd7
This works for dash (and many other shells) :
v=0x12345678
v2=$(( (v<<8 & 0xff00ff00) | (v>>8 & 0xff00ff) ))
v2=$(( (v2<<16 & 0xffff0000) | v2>>16 ))
printf '0x%08x\n' $v2
Result should be "0x78563412"
${v:6:2} is for bash.
In response to Freewind's comment request and building off of hutheano's great answer, I wrote my own bash script and I include a condensed version below. The full script can be downloaded here.
The following implementation accounts for odd length strings, 0x or \x prefixes, and multiple output formats and can be used like the following:
$ be2le d76f411475428afc90947ee320 0xaaff 0xffa '\x3'
20e37e9490fc8a427514416fd7
0xffaa
0xfa0f
\x03
be2le bash script
#!/bin/bash
args=()
format=preserve
delimiter="\n"
nonewline=false
join=false
strip=false
while (( "$#" )); do
case "$1" in
-h|--help) usage;;
-f) format=$2; shift 2;;
--format=*) format="${1#*=}"; shift;;
-d) delimiter=$2; shift 2;;
--delimiter=*) delimiter="${1#*=}"; shift;;
-n|--no-newline) nonewline=true; shift;;
-j|--join) join=true; shift;;
-s|--strip-null) strip=true; shift;;
-*|--*) echo "Error: unsupported flag $1 specified"; exit 1;;
*) args=( "${args[#]}" "$1" ); shift;;
esac
done
case "$format" in
preserve);;
int) prefix="0x";;
char) prefix="\x";;
raw) ;;
*) echo "Error: unsupported format $format"; exit 1;;
esac
n=0
parts=()
for arg in ${args[#]}; do
digest=""
prefix=""
# remove prefix if string begins with "0x"
if [[ $arg =~ ^[0\\]x ]]; then
if [ "$format" == "preserve" ]; then
prefix=${arg:0:2}
fi
arg=${arg:2}
fi
# zero-pad if string has odd length
if [ $[${#arg} % 2] != 0 ]; then
arg="0$arg"
fi
part=""
i=${#arg}
while [ $i -gt 0 ]; do
i=$[$i-2]
byte=${arg:$i:2}
if [ $strip == true ] && [ -z "$part" ] && [ $byte == "00" ]; then
continue
fi
case "$format" in
int) part="$part"'0x'"$byte ";;
char) part="$part\x$byte";;
raw) part="$part$(printf "%b" "\x$byte")";;
*) part="$part$byte";;
esac
done
digest="$prefix$digest$part"
parts=( "${parts[#]}" "$digest" )
n=$[$n+1]
done
if [ $join == true ]; then
case "$format" in
*) printf "%s" "${parts[#]}";;
esac
else
i=0
for part in "${parts[#]}"; do
if [[ $(($i + 1)) < $n ]]; then
printf "%s$delimiter" "$part"
else
printf "%s" "$part"
fi
i=$(($i+1))
done
fi
if [ $nonewline == false ]; then
echo
fi
This script is for flipping 16 bit data.
#!/bin/bash
if [ -t 0 ]; then exit; fi
data=`cat /dev/stdin | od -An -vtx1 | tr -d ' ' | tr -d '\n'`
length=${#data}
i=0
while [ $i -lt $length ]; do
echo -n -e "\x${data:$[$i+2]:2}"
echo -n -e "\x${data:$[$i]:2}"
i=$[$i+4]
done

Resources