confusing statement in shell script - bash

while sizes=`sizes $pgid`
do
set -- $sizes
sample=$((${#/#/+}))
let peak="sample > peak ? sample : peak"
sleep 0.1
done
i am confused about the below statement:
sample=$((${#/#/+}))
could anybody explain this?

The '${#/#/+}' part is a regular expression expansion:
${parameter/pattern/string}
The pattern is expanded to produce a pattern just as in filename expansion.
Parameter is expanded and the longest match of pattern against its value is
replaced with string. If pattern begins with '/', all matches of pattern are replaced
with string. Normally only the first match is replaced. If pattern begins
with '#', it must match at the beginning of the expanded value of parameter.
If pattern begins with '%', it must match at the end of the expanded value of
parameter. If string is null, matches of pattern are deleted and the / following
pattern may be omitted. If parameter is '#' or '*', the substitution operation
is applied to each positional parameter in turn, and the expansion is the resultant
list. If parameter is an array variable subscripted with '#' or '*', the
substitution operation is applied to each member of the array in turn, and the
expansion is the resultant list.
So, it looks like it replaces the empty string at the start of each value in the argument list '$#' with a '+'. It's key merit is that it prefixes each argument in one fell swoop; otherwise, it is similar to "+$var".
The '$(( ... )) part is an arithmetic expression. It performs arithmetic on the expression between the parentheses. So, in context, it adds up the values in the argument list, assuming they are all numeric. Given the expansion, it might yield:
set -- 2 3 5 7 11
sample=$((${#/#/+}))
sample1=$((+2 +3 +5 +7 +11))
echo $sample = $sample1
and hence '28 = 28'.

Let's take the line from the inside out.
${#/#/+}
This is a parameter expansion, which expands the $# parameter (which in this case, will be an array of all of the items in $sizes), and then does a pattern match on each item, replacing each matched sequence with +. The # in the pattern matches the beginning of each item in the input; it doesn't actually consume anything, so the replacement by + will just add a + before each item. You can see this in action with a simple test function:
$ function tst() { echo ${#/#/+}; }
$ tst 1 2 3
+1 +2 +3
The result of this is then substituted into $(( )), which performs arithmetic expansion, evaluating the expression within it. The end result is that the variable $sample is set to the sum of all of the numbers in $sizes.

It's an arithmetic expansion of a string replacement.
$(( )) is arithmetic expansion - eg echo $((1 + 2)).
${var/x/y} is a string replacement; in this case, replace the first # in a line with +. $# is a variable that in this case contains $sizes; this will replace the string and then looks like it will add the values in it.

${var/old/new} expands $var, changing any "old" to "new".
${var/#old/new} insists that the match start at the start of the value
${var/#/new} substitutes at the start of every variable
${#/#/new} (and $#) applies to each parameter
$(( 1 + 3 )) replaces with the arithmetic result.
$(( ${#/#/+/ ))
Expands $#, the arguments from set -- $sizes, prepends a "+" to each parameter and runs the result through an arithmetic evaluation. It looks like it is adding all values on each line.

Related

Replace Last Occurrence of Substring in String (bash)

From the bash software manual:
${parameter/pattern/string}
The pattern is expanded to produce a
pattern just as in filename expansion. Parameter is expanded and the
longest match of pattern against its value is replaced with string.
... If pattern begins with ‘%’, it must match
at the end of the expanded value of parameter.
And so I've tried:
local new_name=${file/%old/new}
Where string is an absolute file path (/abc/defg/hij and old and new are variable strings.
However this seems to be trying to match the literal %sb1.
What is the syntax for this?
Expected Output:
Given
old=sb1
new=sb2
Then
/foo/sb1/foo/bar/sb1 should become /foo/sb1/foo/bar/sb2
/foo/foosb1other/foo/bar/foosb1bar should become /foo/foosb1other/foo/bar/foosb2bar
Using only shell-builtin parameter expansion:
src=sb1; dest=sb2
old=/foo/foosb1other/foo/bar/foosb1bar
if [[ $old = *"$src"* ]]; then
prefix=${old%"$src"*} # Extract content before the last instance
suffix=${old#"$prefix"} # Extract content *after* our prefix
new=${prefix}${suffix/"$src"/"$dest"} # Append unmodified prefix w/ suffix w/ replacement
else
new=$old
fi
declare -p new >&2
...properly emits:
declare -- new="/foo/foosb1other/foo/bar/foosb2bar"

BASH - Capture string between a FIXED and 2 possible variables

To get what is between "aa=" and either % or empty
string = "aa=value%bb"
string2 = "bb=%aa=value"
The rule must work on both strings to get the value of "aa="
I would like a BASH LANGUAGE solution if possible.
Use this:
result=$(echo "$string" | grep -o 'aa=[^%]*')
result=${result:3} # remove aa=
[^%]* matches any sequence of characters that doesn't contain %, so it will stop when it gets to % or the end of the string. $(result:3} expands to the substring starting from character 3, which removes aa= from the beginning.

bash - brace expansion not expanding?

On Kubuntu 15.10
echo $BASH_VERSION
4.3.42(1)-release
I try
reps=1
threads={1}{10..60..10}
for((r=0;r<$reps;r++))
do
tCount=0
for t in $threads
do
echo "t=$t, tCount=${tCount}"
#do something funny with it
((tCount++))
done
done
and it produces a single line
t={1}{10..60..10}, tCount=0
How do I get this to work?
edit
I expect
t=1, tCount=0
t=10, tCount=1
t=20, tCount=2
t=30, tCount=3
t=40, tCount=4
t=50, tCount=5
t=60, tCount=6
update
note that threads=({1}{10..60..10})
and then for t in ${threads[#]}
will prefix the 10..60..10 range with the string {1}
(i.e. {1}10,{1}20,..,{1}60 )
The {1} expression is just a string, since it doesn't conform to the brace expansion syntax:
A sequence expression takes the form {X..Y[..INCR]}, where X and Y are either integers or single characters, and INCR, an optional increment, is an integer.
The brace expansion is performed before variable expansion, so you can't expand braces by just referring to a variable:
The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion.
Whether write the brace expansion directly, or use eval (usually not recommended).
Example:
tCount=0
for t in {1,{10..60..10}}; do
echo "t=$t tCount=$tCount"
(( tCount++ ))
done

Why does IFS not affect the length of an array in bash?

I have two specific questions about the IFS. I'm aware that changing the internal field separator, IFS, changes what the bash script iterates over.
So, why is it that the length of the array doesn't change?
Here's my example:
delimiter=$1
strings_to_find=$2
OIFS=$IFS
IFS=$delimiter
echo "the internal field separator is $IFS"
echo "length of strings_to_find is ${#strings_to_find[#]}"
for string in ${strings_to_find[#]}
do
echo "string is separated correctly and is $string"
done
IFS=$OIFS
But why does the length not get affected by the new IFS?
The second thing that I don't understand is how to make the IFS affect the input arguments.
Let's say I'm expecting my input arguments to look like this:
./executable_shell_script.sh first_arg:second_arg:third_arg
And I want to parse the input arguments by setting the IFS to :. How do I do this? Setting the IFS doesn't seem to do anything. I must be doing this wrong....?
Thank you.
Bash arrays are, in fact, arrays. They are not strings which are parsed on demand. Once you create an array, the elements are whatever they are, and they won't change retroactively.
However, nothing in your example creates an array. If you wanted to create an array out of argument 2, you would need to use a different syntax:
strings_to_find=($2)
Although your strings_to_find is not an array, bash allows you to refer to it as though it were an array of one element. So ${#strings_to_find[#]} will always be one, regardless of the contents of strings_to_find. Also, your line:
for string in ${strings_to_find[#]}
is really no different from
for string in $strings_to_find
Since that expansion is not quoted, it will be word-split, using the current value of IFS.
If you use an array, most of the time you will not want to write for string in ${strings_to_find[#]}, because that just reassembles the elements of an array into a string and then word-splits them again, which loses the original array structure. Normally you will avoid the word-splitting by using double quotes:
strings_to_find=(...)
for string in "${strings_to_find[#]}"
As for your second question, the value of IFS does not alter the shell grammar. Regardless of the value of IFS, words in a command are separated by unquoted whitespace. After the line is parsed, the shell performs parameter and other expansions on each word. As mentioned above, if the expansion is not quoted, the expanded text is then word-split using the value of IFS.
If the word does not contain any expansions, no word-splitting is performed. And even if the word does contain expansions, word-splitting is only performed on the expansion itself. So, if you write:
IFS=:
my_function a:b:c
my_function will be called with a single argument; no expansion takes places, so no word-splitting occurs. However, if you use $1 unquoted inside the function, the expansion of $1 will be word-split (if it is expanded in a context in which word-splitting occurs).
On the other hand,
IFS=:
args=a:b:c
my_function $args
will cause my_function to be invoked with three arguments.
And finally,
IFS=:
args=c
my_function a:b:$args
is exactly the same as the first invocation, because there is no : in the expansion.
This is an example script based on #rici's answer :
#!/bin/bash
fun()
{
echo "Total Params : " $#
}
fun2()
{
array1=($1) # Word splitting occurs here based on the IFS ':'
echo "Total elements in array1 : "${#array1[#]}
# Here '#' before array counts the length of the array
array2=("$1") # No word splitting because we have enclosed $1 in double quotes
echo "Total elements in array2 : "${#array2[#]}
}
IFS_OLD="$IFS"
IFS=$':' #Changing the IFS
fun a:b:c #Nothing to expand here, so no use of IFS at all. See fun2 at last
fun a b c
fun abc
args="a:b:c"
fun $args # Expansion! Word splitting occurs with the current IFS ':' here
fun "$args" # preventing word spliting by enclosing ths string in double quotes
fun2 a:b:c
IFS="$IFS_OLD"
Output
Total Params : 1
Total Params : 3
Total Params : 1
Total Params : 3
Total Params : 1
Total elements in array1 : 3
Total elements in array2 : 1
Bash manpage says :
The shell treats each character of IFS as a delimiter, and splits the
results of the other expansions into words on these characters.

Replace " " with "\ " in a file path string with variable expansion

I know there is a better way to do this.
What is the better way?
How do you do a string replace on a string variable in bash?
For Example: (using php because that's what I know)
$path = "path/to/directory/foo bar";
$path = str_replace(" ", "\ ", "$path");
echo $path;
returns:
path/to/directory/foo\ bar
To perform the specific replacement in bash:
path='path/to/directory/foo bar'
echo "${path// /\\ }"
Don't use prefix $ when assigning to variables in bash.
No spaces are allowed around the =.
Note that path is assigned with single quotes, whereas the string replacement occurs in double quotes - this distinction is important: bash does NOT interpret single-quoted strings, whereas you can refer to variables (and do other things) in double-quoted strings; (also, not quoting a variable reference at all has other ramifications, often undesired - in general, double-quote your variable references)
Explanation of string replacement "${path// /\\ }":
In order to perform value substitution on a variable, you start with enclosing the variable name in {...}
// specifies that ALL occurrences of the following search pattern are to be replaced (use / to replace the first occurrence only).
/ separates the search pattern, (a single space), from the replacement string, \\ .
The replacement string, \ , must be represented as \\ , because \ has special meaning as an escape char. and must therefore itself be escaped for literal use.
The above is an instance of what bash (somewhat cryptically) calls shell parameter expansion and also parameter expansion and [parameter and] variable expansion. There are many more flavors, such as for extracting a substring, providing a default value, stripping a prefix or suffix, ... - see the BashGuide page on the topic or the manual.
As for what types of expressions are supported in the search and replacement strings:
The search expression is a globbing pattern of the same type used in filename expansion (e.g, *.txt); for instance, v='dear me'; echo "${v/m*/you}" yields 'dear you'. Note that the longest match will be used.
Additionally, the first character of the pattern has special meaning in this context:
/, as we've seen above, causes all matching occurrences of the pattern to be replaced - by default, only the first one is replaced.
# causes the rest of the pattern to only match at the beginning of the input variable
% only matches at the end
The replacement expression is a string that is subject to shell expansions; while there is no support for backreferences, the fact that the string is expanded allows you to have the replacement string reference other variables, contain commands, with $(...), ...; e.g.:
v='sweet home'; echo "${v/home/$HOME}" yields, for instance, 'sweet /home/jdoe'.
v='It is now %T'; echo "${v/\%T/$(date +%T)}" yields, for instance, It is now 10:05:17.
o1=1 o2=3 v="$o1 + $o2 equals result"; echo "${v/result/$(( $o1 + $o2 ))}" yields '1 + 3 equals 4' (I think)
There are many more features and subtleties - refer to the link above.
How about sed? Is that what you're looking for?
#!/bin/bash
path="path/to/directory/foo bar"
new_path=$(echo "$path" | sed 's/ /\\ /g')
echo "New Path: '$new_path"
But as #n0rd pointed out in his comment, is probably better just quoting the path when you want to use it; something like...
path="path/to/directory/foo bar"
echo "test" > "$path"

Resources