Looking for a breakdown of echo{,} foo - string

How does the expression {,} work?
I tried searching around SOF as well as on google and even the man pages for bash, yet I haven't been able to come across an explanation for this.
From fiddling around with the expression I've learned that it's some sort of string copying function.
echo{,} foo
>> echo echo foo
echo foo{,}
>> foo foo
How does this expression work? Is there a name for this? Also, can you provide a practical example for the use of this function?

It's called brace expansion.
Here's a simple explanation.
{,} will repeat left adjacent string exactly 2 times by appending empty strings.
Similarly {,,} will repeat left adjacent string exactly 3 times.
var{A,B} will return varA and varB
And these examples should clarify more:
echo foo{,,}
foo foo foo
echo foo{,1}
foo foo1
echo foo{1,2}
foo1 foo2
echo foo{,,}
foo foo foo
echo foo{1,2,3}
foo1 foo2 foo3
Link to the docs

The "normal" use case for brace expansion is to repeat a string with a substring replaced by each element of a list given in curly braces:
$ echo file.{txt,dat,log}
file.txt file.dat file.log
Now, if one of the list elements is empty, the string gets printed as is:
$ echo file.{txt,dat,}
file.txt file.dat file.
A popular use case for this is to rename a file
$ mv -v file.txt{,.bak}
'file.txt' -> 'file.txt.bak'
This expands to mv -v file.txt file.txt.bak, because the first list element is empty.
Now, if all elements are empty as in {,}, the string just gets printed as many times as there are list elements.

Related

Bash - Multi-character string replacement when strings consist of unknown length but same character

Assume a multi-line text string in which some lines start with a key-character ("#" in our case). Further assume that you wish to replace all instances of a target character ("o" in our case) with a different character ("O" in our case), if - and only if - that target character occurs as a string of two or more adjacent copies (e.g., "ooo"). This replacement is to be done in all lines that do not start with the key-character and must be case-sensitive.
For example, the following lines ...
#Foo bar
Foo bar
#Baz foo
Baz foo
are supposed to be converted into:
#Foo bar
FOO bar
#Baz foo
Baz fOO
The following attempt using sed does not retain the correct number of target characters:
$ echo -e "#Foo bar\nFoo bar\n#Baz foo\nBaz foo" | sed '/^#/!s/o\{2,\}/O/g'
#Foo bar
FO bar
#Baz foo
Baz fO
What code (with sed or otherwise) would conduct the desired replacement correctly?
You can use Perl:
echo -e "#Foo bar\nFoo bar\n#Baz foo\nBaz foo" | perl -pe 's/^#.*(*SKIP)(*F)|o{2,}/"O" x length($&)/ge'
Here, ^#.*(*SKIP)(*F) matches and skips all lines starting with #, then o{2,} matches two or more o chars, and "O" x length($&) replaces these matches with O that is repeated the match size times ($& is the match value). Note the e flag after g that is used to evaluate the string on the right-hand side.
See the online demo:
#!/bin/bash
s="#Foo bar
Foo bar
#Baz foo
Baz foo"
perl -pe 's/^#.*(*SKIP)(*F)|o{2,}/"O" x length($&)/ge' <<< "$s"
Output:
#Foo bar
FOO bar
#Baz foo
Baz fOO
Using sed
$ echo -e "#Foo bar\nFoo bar\n#Baz foo\nBaz foo" | sed '/#/!s/o\{2\}/\U&/'
#Foo bar
FOO bar
#Baz foo
Baz fOO
This might work for you (GNU sed):
sed -E '1{x;s/^/O/;x}
/^#/b
:a;/oo+/!b;s/oo+/\n&\n/;tb
:b;G;s/\n\n(.*)\n.$/\1/;ta;s/\n[^\n](.*\n.*)\n(.)$/\2\n\1/;tb' file
In overview, use a designated character or characters (in this case O) to replace two or more o's where the start of a line is not #.
Prime the hold space with the designated character.
If the line starts with #, break out.
If the line does not contain two or o's, break out.
Otherwise, surround the two or more o's by newlines.
Append the replacement character and then replace non-newline characters between two newlines with the designated character.
When all replacements for the current set of o's have been replaced, check for more by continuing as above.
Once all replacements have been found, print the amended line.
A solution allowing for multiple replacements:
sed -E '1{x;s/^/oOxX/;x}
/^#/b;
:a;G;/((.)\2+)(.*\n(..)*\2)/!s/\n.*//;t;s//\n\1\n\3/;tb
:b;s/\n\n(.*)\n.*$/\1/;ta;s/\n(.)(.*\n.*\n(..)*\1(.))/\4\n\2/;tb' file

How to find a occurrence of a word using grep in a case sensitive manner

I've a file, with below content in it.
foo, FOO, Foo, FOo and i want to search with the lines which matches with the FOo, ie the 4th word in my file.
How can i achieve it using grep ?
simply do
grep "FOo" file
grep by default does a case sensitive search. It matches all the lines that will have FOo in it.
Input:
$ cat file
hello foo
meow Foo
bhow FOo
ding dong Foo
the last one FOO
Output:
$ grep FOo file
bhow FOo
TIP:
If you want to do case-insensitive search use grep -i

How do you interpret ${VAR#*:*:*} in Bourne Shell

I am using Bourne Shell. Need to confirm if my understanding of following is correct?
$ echo $SHELL
/bin/bash
$ VAR="NJ:NY:PA" <-- declare an array with semicolon as separator?
$ echo ${VAR#*} <-- show entire array without separator?
NJ:NY:PA
$ echo ${VAR#*:*} <-- show array after first separator?
NY:PA
$ echo ${VAR#*:*:*} <-- show string after two separator
PA
${var#pattern} is a parameter expansion that expands to the value of $var with the shortest possible match for pattern removed from the front of the string.
Thus, ${VAR#*:} removes everything up and including to the first :; ${VAR#*:*:} removes everything up to and including the second :.
The trailing *s on the end of the expansions given in the question don't have any use, and should be avoided: There's no reason whatsoever to use ${var#*:*:*} instead of ${var#*:*:} -- since these match the smallest amount of text possible, and * is allowed to expand to 0 characters, the final * matches and removes nothing.
If what you really want is an array, you might consider using a real array instead.
# read contents of string VAR into an array of states
IFS=: read -r -a states <<<"$VAR"
echo "${states[0]}" # will echo NJ
echo "${states[1]}" # will echo NY
echo "${#states[#]}" # count states; will emit 3
...which also gives you the ability to write:
printf ' - %s\n' "${states[#]}" # put *all* state names into an argument list

Bash: Replace word with spaces equal to the length of the word

I thought my bash-fu was strong enough but apparently it isn't. I can't seem to figure this out. I would like to do something like this:
var="XXXX This is a line"
word_to_replace="XXXX"
# ...do something
echo "Done:${var}"
Done: This is a line
Basically I want to quickly replace all characters in a word with spaces, preferably in one step. Note, if it makes things easier var currently will be at the start of the string although it may have leading spaces (which would need to be retained).
In python I would possibly do this:
>>> var="XXXX This is a line"
>>> word_to_replace="XXXX"
>>> var=var.replace(word_to_replace, ' '*len(word_to_replace))
>>> print("Done:%s" % var)
Done: This is a line
Here's one way you could do it, using a combination of shell parameter expansion and the sed command.
$ var="XXXX This is a line"
$ word_to_replace="XXXX"
$ replacement=${word_to_replace//?/ }
$ sed "s/$word_to_replace/$replacement/" <<<"$var"
This is a line
? matches any character and ${var//find/replace} does a global substitution, so the variable $replacement has the same length as $word_to_replace, but is composed solely of spaces.
You can save the result to a variable in the usual way:
new_var=$(sed "s/$word_to_replace/$replacement/" <<<"$var")
In plain Bash:
If we know the word to be replaced:
$ line=" foo and some"
$ word=foo
$ spaces=$(printf "%*s" ${#word} "")
$ echo "${line/$word/$spaces}"
and some
If we don't, we could pick the string apart to find the leading word, but this gets a bit ugly:
xxx() {
shopt -s extglob # for *( )
local line=$1
local indent=${line%%[^ ]*} # the leading spaces
line=${line##*( )} # remove the leading spaces
local tail=${line#* } # part after first space
local head=${line%% *} # part before first space...
echo "$indent${head//?/ } $tail" # replace and put back together
}
$ xxx " word on a line"
on a line
That also fails if there is only one word on the line, head and tail both get set to that word, we'd need to check for if there is a space and handle the two cases separately.
Using sed:
#!/usr/bin/env sh
word_to_replace="XXXX"
var="$word_to_replace This is a line"
echo "Done: $var"
word_to_replace=$(echo "$word_to_replace" | sed 's,., ,g')
var="$word_to_replace This is a line"
echo "Done: $var"
I use GNU Awk:
echo "$title" | gawk '{gsub(/./, "*"); print}'
This replaces each character with an asterisk.
EDIT. Consolidated answer:
$ export text="FOO hello"
$ export sub="FOO"
$ export space=${sub//?/ }
$ echo "${text//$sub/$space}"
hello

IFS and moving through single positions in directory

I have two questions .
I have found following code line in script : IFS=${IFS#??}
I would like to understand what it is exactly doing ?
When I am trying to perform something in every place from directory like eg.:
$1 = home/user/bin/etc/something...
so I need to change IFS to "/" and then proceed this in for loop like
while [ -e "$1" ]; do
for F in `$1`
#do something
done
shift
done
Is that the correct way ?
${var#??} is a shell parameter expansion. It tries to match the beginning of $var with the pattern written after #. If it does, it returns the variable $var with that part removed. Since ? matches any character, this means that ${var#??} removes the first two chars from the var $var.
$ var="hello"
$ echo ${var#??}
llo
So with IFS=${IFS#??} you are resetting IFS to its value after removing its two first chars.
To loop through the words in a /-delimited string, you can store the splitted string into an array and then loop through it:
$ IFS="/" read -r -a myarray <<< "home/user/bin/etc/something"
$ for w in "${array[#]}"; do echo "-- $w"; done
-- home
-- user
-- bin
-- etc
-- something

Resources