In bash, how do I trim a certain section of a string? - linux

I'm trying to trim only the left half of a string that is given to ltrim() as an argument. This is my current code.
ltrim()
{
string=${1}
divider=$((${#string} / 2))
trimrule=${2}
string_left=${string:0:$divider}
string_right=${string:$divider}
echo ${string:$divider} ## My own quick debug lines
echo ${string:0:$divider} ## My own quick debug lines
if [ $# -ne 2 ]
then
printf "%d argument(s) entered. 2 required.\n" "$#"
else
while :
do
case $string_left in
${2}*) string_left=${string_left#?} ;;
*${2}) string_left=${string_left%?} ;;
*) break ;;
esac
done
printf "Left side string is %s\n" "${string_left}"
fi
}
However, when I enter ltrim abcdefghijklmnopq abc the shell returns the following:
ijklmnopq
abcdefgh
Left side string is bcdefgh
So I only lost 'a' out of the word while I'm looking to get 'defgh' as a result. What am I doing wrong?

function substr_remove() {
echo "${1//$2/}"
}
substr_remove carfoobar123foo456 foo
Output:
carbar123456

Are you searching for something like this?
function ltrim() {
echo ${1##$2}
}
ltrim abcdefghijklmnopq abc # Prints: defghijklmnopq

Related

How To Parameter Array To The Function In Bash [duplicate]

As we know, in bash programming the way to pass arguments is $1, ..., $N. However, I found it not easy to pass an array as an argument to a function which receives more than one argument. Here is one example:
f(){
x=($1)
y=$2
for i in "${x[#]}"
do
echo $i
done
....
}
a=("jfaldsj jflajds" "LAST")
b=NOEFLDJF
f "${a[#]}" $b
f "${a[*]}" $b
As described, function freceives two arguments: the first is assigned to x which is an array, the second to y.
f can be called in two ways. The first way use the "${a[#]}" as the first argument, and the result is:
jfaldsj
jflajds
The second way use the "${a[*]}" as the first argument, and the result is:
jfaldsj
jflajds
LAST
Neither result is as I wished. So, is there anyone having any idea about how to pass array between functions correctly?
You cannot pass an array, you can only pass its elements (i.e. the expanded array).
#!/bin/bash
function f() {
a=("$#")
((last_idx=${#a[#]} - 1))
b=${a[last_idx]}
unset a[last_idx]
for i in "${a[#]}" ; do
echo "$i"
done
echo "b: $b"
}
x=("one two" "LAST")
b='even more'
f "${x[#]}" "$b"
echo ===============
f "${x[*]}" "$b"
The other possibility would be to pass the array by name:
#!/bin/bash
function f() {
name=$1[#]
b=$2
a=("${!name}")
for i in "${a[#]}" ; do
echo "$i"
done
echo "b: $b"
}
x=("one two" "LAST")
b='even more'
f x "$b"
You can pass an array by name reference to a function in bash (since version 4.3+), by setting the -n attribute:
show_value () # array index
{
local -n myarray=$1
local idx=$2
echo "${myarray[$idx]}"
}
This works for indexed arrays:
$ shadock=(ga bu zo meu)
$ show_value shadock 2
zo
It also works for associative arrays:
$ declare -A days=([monday]=eggs [tuesday]=bread [sunday]=jam)
$ show_value days sunday
jam
See also nameref or declare -n in the man page.
You could pass the "scalar" value first. That would simplify things:
f(){
b=$1
shift
a=("$#")
for i in "${a[#]}"
do
echo $i
done
....
}
a=("jfaldsj jflajds" "LAST")
b=NOEFLDJF
f "$b" "${a[#]}"
At this point, you might as well use the array-ish positional params directly
f(){
b=$1
shift
for i in "$#" # or simply "for i; do"
do
echo $i
done
....
}
f "$b" "${a[#]}"
This will solve the issue of passing array to function:
#!/bin/bash
foo() {
string=$1
array=($#)
echo "array is ${array[#]}"
echo "array is ${array[1]}"
return
}
array=( one two three )
foo ${array[#]}
colors=( red green blue )
foo ${colors[#]}
Try like this
function parseArray {
array=("$#")
for data in "${array[#]}"
do
echo ${data}
done
}
array=("value" "value1")
parseArray "${array[#]}"
Pass the array as a function
array() {
echo "apple pear"
}
printArray() {
local argArray="${1}"
local array=($($argArray)) # where the magic happens. careful of the surrounding brackets.
for arrElement in "${array[#]}"; do
echo "${arrElement}"
done
}
printArray array
Here is an example where I receive 2 bash arrays into a function, as well as additional arguments after them. This pattern can be continued indefinitely for any number of bash arrays and any number of additional arguments, accommodating any input argument order, so long as the length of each bash array comes just before the elements of that array.
Function definition for print_two_arrays_plus_extra_args:
# Print all elements of a bash array.
# General form:
# print_one_array array1
# Example usage:
# print_one_array "${array1[#]}"
print_one_array() {
for element in "$#"; do
printf " %s\n" "$element"
done
}
# Print all elements of two bash arrays, plus two extra args at the end.
# General form (notice length MUST come before the array in order
# to be able to parse the args!):
# print_two_arrays_plus_extra_args array1_len array1 array2_len array2 \
# extra_arg1 extra_arg2
# Example usage:
# print_two_arrays_plus_extra_args "${#array1[#]}" "${array1[#]}" \
# "${#array2[#]}" "${array2[#]}" "hello" "world"
print_two_arrays_plus_extra_args() {
i=1
# Read array1_len into a variable
array1_len="${#:$i:1}"
((i++))
# Read array1 into a new array
array1=("${#:$i:$array1_len}")
((i += $array1_len))
# Read array2_len into a variable
array2_len="${#:$i:1}"
((i++))
# Read array2 into a new array
array2=("${#:$i:$array2_len}")
((i += $array2_len))
# You can now read the extra arguments all at once and gather them into a
# new array like this:
extra_args_array=("${#:$i}")
# OR you can read the extra arguments individually into their own variables
# one-by-one like this
extra_arg1="${#:$i:1}"
((i++))
extra_arg2="${#:$i:1}"
((i++))
# Print the output
echo "array1:"
print_one_array "${array1[#]}"
echo "array2:"
print_one_array "${array2[#]}"
echo "extra_arg1 = $extra_arg1"
echo "extra_arg2 = $extra_arg2"
echo "extra_args_array:"
print_one_array "${extra_args_array[#]}"
}
Example usage:
array1=()
array1+=("one")
array1+=("two")
array1+=("three")
array2=("four" "five" "six" "seven" "eight")
echo "Printing array1 and array2 plus some extra args"
# Note that `"${#array1[#]}"` is the array length (number of elements
# in the array), and `"${array1[#]}"` is the array (all of the elements
# in the array)
print_two_arrays_plus_extra_args "${#array1[#]}" "${array1[#]}" \
"${#array2[#]}" "${array2[#]}" "hello" "world"
Example Output:
Printing array1 and array2 plus some extra args
array1:
one
two
three
array2:
four
five
six
seven
eight
extra_arg1 = hello
extra_arg2 = world
extra_args_array:
hello
world
For further examples and detailed explanations of how this works, see my longer answer on this topic here: Passing arrays as parameters in bash
You can also create a json file with an array, and then parse that json file with jq
For example:
my-array.json:
{
"array": ["item1","item2"]
}
script.sh:
ARRAY=$(jq -r '."array"' $1 | tr -d '[],"')
And then call the script like:
script.sh ./path-to-json/my-array.json

Parsing long and short args in ksh using loop

I am trying to parse arguments in ksh. Can't do getopt for the same as in short options I have two/three characters. Currently I am using for loop. Its stupid but am unable to find something better.
Question: How do I set option+value as one unit in order to parse?
Also if eval set -- $option will help me then how do I use it? echo on option does not show the expected "--" at the end. Am I assuming something wrong?
I am thinking of using a variable to keep track of when an option is found but this method seems too confusing and unnecessary.
Thanks for your time and help.
Update 1:
Adding code as pointed out. Thanks to markp, Andre Gelinas and random down-voter in making this question better. Trying to execute the script as given in line 2 and 3 of code - or any other combination of short and long options passed together.
#!/bin/ksh
# bash script1.sh --one 123 --two 234 --three "some string"
# bash script1.sh -o 123 -t 234 -th "some string"
# the following creates problems for short options.
#options=$(getopt -o o:t:th: -l one:two:three: "--" "$#")
#Since the below `eval set -- "$options"` did not append "--" at the end
#eval set -- "$options"
for i in $#; do
options="$options $i"
done
options="$options --"
# TODO capture args into variables
Attempted code below TODO until now:
for i in $options; do
echo $i
done
Will be capturing the args using:
while true; do
case $1 in
--one|-o) shift; ONE=$1
;;
--two|-t) shift; TWO=$1
;;
--three|-th) shift; THREE=$1
;;
--) shift; break
;;
esac
done
Try something like this :
#!/bin/ksh
#Default value
ONE=123
TWO=456
# getopts configuration
USAGE="[-author?Andre Gelinas <andre.gelinas#foo.bar>]"
USAGE+="[-copyright?2018]"
USAGE+="[+NAME?TestGetOpts.sh]"
USAGE+="[+DESCRIPTION?Try out for GetOps]"
USAGE+="[o:one]#[one:=$ONE?First.]"
USAGE+="[s:second]#[second:=$TWO?Second.]"
USAGE+="[t:three]:[three?Third.]"
USAGE+=$'[+SEE ALSO?\aman\a(1), \aGetOpts\a(1)]'
while getopts "$USAGE" optchar ; do
case $optchar in
o) ONE=$OPTARG ;;
s) TWO=$OPTARG ;;
t) THREE=$OPTARG ;;
esac
done
print "ONE = "$ONE
print "TWO = "$TWO
print "THREE = "$THREE
You can use either --one or -o. Using --man or --help are also working. Also -o and -s are numeric only, but -t will take anything. Hope this help.

Passing quoted as arguments to the function

I would like to find out answer on probably quite simple question: I would like to pass quoted strings with whitespaces inside as a standalone arguments for function.
There is the following file with data (for example):
one
two three
four five six
seven
And there is script with 2 simple functions:
params_checker()
{
local first_row="$1"
local second_row="$2"
local third_row="$3"
echo "Expected args are:${first_row} ; ${second_row} ; ${third_row}"
echo "All args are:"
for arg in "$#"; do
echo "${arg}"
done
}
read_from_file()
{
local args_string
while read line; do
args_string="${args_string} \"${line}\""
echo "Read row: ${line}"
done < ./test_input
params_checker ${args_string}
}
read_from_file
In other words I would like to get rows from text file as arguments to function params_checker (each row from file as different parameter, I need to keep whitespaces in the rows). Attempt to make combined string with quoted "substrings" was failed, and output was:
~/test_sh$ sh test_process.sh
Read row: one
Read row: two three
Read row: four five six
Read row: seven
Expected args are:"one" ; "two ; three"
All args are:
"one"
"two
three"
"four
five
six"
"seven"
Expectation is $1="one", $2="two three", $3="four five six" ...
Quoting of ${args_string} during passing to params_checker gave another result, string is passed as a single argument.
Could you please help to find out correct way how to pass such strings with whitespaces from file as a different standalone function argumets?
Thanks a lot for help!
In bash/ksh/zsh you'd use an array. In sh, you can use the parameters "$1", "$2" etc:
read_from_file()
{
set -- # Clear parameters
while read line; do
set -- "$#" "$line" # Append to the parameters
echo "Read row: ${line}"
done < ./test_input
params_checker "$#" # Pass all parameters
}
There you go, this should give you what you are looking for:
#!/bin/bash
params_checker()
{
local first_row="$1"
local second_row="$2"
local third_row="$3"
local forth_row="$4"
echo "Expected args are: ${first_row} ; ${second_row} ; ${third_row} ; ${forth_row}"
echo "All args are:"
for i in "$#"
do
echo "$i"
done
}
read_from_file()
{
ARRAY=()
while read line; do
echo "Read row: ${line}"
ARRAY+=("$line")
done < ./test_input
params_checker "${ARRAY[#]}"
}
read_from_file;
That should work fine in BASH. If your file is named test.sh, you can run it like this ./test.sh

String manipulation in Bash with prefixes

I have a String with the folowing String : aaaccbbaabbbb
I need to either drop the front aaa's or the character sequence in the back bbbb's. I've tried resString=(${resString%%b*b}) which resString is aaaccbbaabbbb is turned into aaaccbbaa. But I need to save the deleted bbbb's into a file. Is there a way to inverse the outcome of resString=(${resString%%b*b}) to get the bbbb in a file. I've tried working with ## manipulation but it's such a hassle since I only need the repetition in the front or at the back of the String.
You could use bash regex matching:
resString='abababbbb'
if [[ $resString =~ [^b](b+)$ ]] ; then
resString=${BASH_REMATCH[1]}
fi
echo $resString
This prints bbbb.
You can use parameter expansion with extended globbing:
#!/bin/bash
shopt -s extglob # Turn extended globbing on.
s=aaaccbbaabbbb
prefix=${s%%[^a]*} # Remove everything from the first non-"a".
prefix_rest=${s##+(a)} # Remove all a's at the beginning.
suffix=${s##*[^b]} # See above.
suffix_rest=${s%%+(b)}
[[ $prefix$prefix_rest == $s ]] || echo Wrong prefix
[[ $suffix_rest$suffix == $s ]] || echo Wrong suffix
echo "$prefix : $prefix_rest"
echo "$suffix_rest : $suffix"
Ok, a whole new approach using ${//}.
It is fully automatic, it finds which is the first char, and which is the last.
With that set, it works its magic to select runs in the front and runs in the back.
Of course, you need to edit the program to choose which parts you do need to send to file, or print, or anything else. I hope you could do that part of the job.
This seems to work with any string (even repeated chars):
#!/bin/bash
a=(aaaccbbaabbbb aaabbbbaaaa abababbbb bbbaaabbb aaaaaa aaabbbbaaaa)
for resString in "${a[#]}"; do
echo
echo "String :$resString:"
l="$((2+${#resString}))"
frontchar=${resString:0:1} ; printf "%s%-${l}s\n" "Frontchar" ":$frontchar:"
backchar=${resString:0-1:1} ; printf "%s%${l}s\n" "Backchar " ":$backchar:"
head="${resString/%[^$frontchar]*}"; printf "%s%-${l}s\n" "head " ":$head:"
tail="${resString/#*[^$backchar]}" ; printf "%s%${l}s\n" "tail " ":$tail:"
prefix="${resString%$tail}" ; printf "%s%-${l}s\n" "prefix " ":$prefix:"
suffix="${resString#$head}" ; printf "%s%${l}s\n" "suffix " ":$suffix:"
echo "Using the head/suffix value: $head -- $suffix"
echo "Using the prefix/tail value: $prefix -- $tail"
done
Running it, you get:
String :aaabbbbaaaa:
Frontchar:a:
Backchar :a:
head :aaa:
tail :aaaa:
prefix :aaabbbb:
suffix :bbbbaaaa:
Using the head/suffix value: aaa -- bbbbaaaa
Using the prefix/tail value: aaabbbb -- aaaa
String :aaaccbbaabbbb:
Frontchar:a:
Backchar :b:
head :aaa:
tail :bbbb:
prefix :aaaccbbaa:
suffix :ccbbaabbbb:
Using the head/suffix value: aaa -- ccbbaabbbb
Using the prefix/tail value: aaaccbbaa -- bbbb
String :abababbbb:
Frontchar:a:
Backchar :b:
head :a:
tail :bbbb:
prefix :ababa:
suffix :bababbbb:
Using the head/suffix value: a -- bababbbb
Using the prefix/tail value: ababa -- bbbb
String :aaaaaa:
Frontchar:a:
Backchar :a:
head :aaaaaa:
tail :aaaaaa:
prefix ::
suffix ::
Using the head/suffix value: aaaaaa --
Using the prefix/tail value: -- aaaaaa

Linux Shell Script - String Comparison with wildcards

I am trying to see if a string is part of another string in shell script (#!bin/sh).
The code i have now is:
#!/bin/sh
#Test scriptje to test string comparison!
testFoo () {
t1=$1
t2=$2
echo "t1: $t1 t2: $t2"
if [ $t1 == "*$t2*" ]; then
echo "$t1 and $t2 are equal"
fi
}
testFoo "bla1" "bla"
The result I'm looking for, is that I want to know when "bla" exists in "bla1".
Thanks and kind regards,
UPDATE:
I've tried both the "contains" function as described here: How do you tell if a string contains another string in Unix shell scripting?
As well as the syntax in String contains in bash
However, they seem to be non compatible with normal shell script (bin/sh)...
Help?
When using == or != in bash you can write:
if [[ $t1 == *"$t2"* ]]; then
echo "$t1 and $t2 are equal"
fi
Note that the asterisks go on the outside of the quotes and that the wildcard pattern must be on the right.
For /bin/sh, the = operator is for equality only, not pattern matching. You can use case for pattern matching though:
case "$t1" in
*"$t2"*) echo t1 contains t2 ;;
*) echo t1 does not contain t2 ;;
esac
If you're specifically targeting Linux, I would assume the presence of /bin/bash.

Resources