read[command] in bash - linux

When I read[command] some lines including character '*', it seems that '*' will be looked as a wildcard. whether exsits some solutions leting '*' just be a '*', please!

It depends how you use the variable: if you quote it, filename expansion will not happen. Example:
$ ls
f1 f2 f3
$ read line
*
$ echo "$line"
*
$ echo $line
f1 f2 f3

If you do not want any of the special file name characters to be used as wildcards then enter the following in your script before the read.
set -o noglob
This will prevent the * ? and [] from having special meaning and treat them as normal characters.
The following example demonstrates the point
touch 1 2 3
echo "With wild card expansion"
echo *
echo "Without wild card expansion"
set -o noglob
echo *
And produces the following results
With wild card expansion
1 2 3
Without wild card expansion
*

You can escape it with the escape character: \*. This means the * will be a literal *, not matching one or more characters as the glob pattern.

Related

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

Linux for loop function in .bashrc [duplicate]

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Closed 5 years ago.
#!/bin/sh
for i in {1..5}
do
echo "Welcome"
done
Would work, displays Welcome 5 times.
#!/bin/sh
howmany=`grep -c $1 /root/file`
for i in {1..$howmany}
do
echo "Welcome"
done
Doesn't work! howmany would equal 5 as that is what the output of grep -c would display. $1 is parameter 1 which is specific when running the script.
Any ideas?
Workarounds for not being able to use variables in a sequence brace expression:
If the intent is merely to iterate over numbers in a range - as in the OP's case - the best choice is not to use brace expansion, but instead use bash's C-style loop - see user000001's answer.
If the specific numbers aren't important and you simply need to execute a loop body a specified number of times, Cole Tierney's answer is an option.
If use of brace expansion is desired nonetheless:
If you do NOT need the numbers in the list to have a prefix or postfix, use the seq utility with an unquoted command substitution (small caveat: seq is NOT a POSIX utility, but it is widely available); e.g.
echo $(seq 3) -> 1 2 3; start number 1 implied
echo $(seq -f '%02.f' 3) -> 01 02 03 - zero-padded
echo $(seq 2 4) -> 2 3 4; explicit start and end numbers
echo $(seq 1 2 5) -> 1 3 5; custom increment (the 2 in the middle)
If you DO need the numbers in the list to have a prefix or postfix, you have several choices:
Use the seq utility with its -f option for providing a printf-style format string (as used above for zero-padding), or pure Bash workarounds based on eval (extra care needed!) or building an array in a loop, all of which are detailed in this answer.
You could also consider implementing the functionality generically, such as by writing a custom shell function or a custom script with utilities such as awk or perl.
Example of safe use of eval with variables driving a sequence brace expression:
The variables are validated beforehand, to make sure they contain decimal integers.
from=1 to=3 # sample values
# Ensure that $from and $to are decimal numbers and abort, if they are not.
(( 10#$from + 10#$to || 1 )) 2>/dev/null || { echo "Need decimal integers" >&2; exit 1; }
eval echo "A{$from..$to}" # -> 'A1 A2 A3'
General overview of brace expansion
The main purpose of brace expansion is to expand to a list of tokens with each token having an optional prefix and/or postfix; brace expansions must be unquoted and come in 2 flavors:
a fixed series (list) of comma-separated strings - variables supported
specifies and expands to a fixed number of tokens (2 or more); e.g.:
echo A{b,c,d} -> Ab Ac Ad, i.e., 3 tokens, as implied by the number of args.
echo {/,$HOME/}Library e.g., -> /Library /User/jdoe/Library
Variable references - and even globs - are supported, but note that they get expanded after brace expansion, in its result, in the course of normal evaluation.
a sequence expression (range) with .., typically numerical - variables NOT supported
expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
[rare] strings: only single English letters allowed; e.g. {a..c}
numbers: decimal integers only; e.g., {1..10}, {10..1}, {-1..2}
example with prefix and postfix: A{1..3}# -> A1# A2# A3#
broken example with variables: {$from..$to} # !! FAILS - $from and $to are interpreted as literals and therefore not recognized as either a single letter or a decimal integer - no brace expansion is performed (see below).
by contrast, using variables does work in zsh and ksh.
bash 4+ adds two features:
optional increment step value:
echo A{1..5..2} -> A1 A3 A5 - numbers incremented by 2
ability to zero-pad:
echo A{001..003} -> A001 A002 A003
An invalid brace expression is not expanded (treated like a regular unquoted string, with { and } treated as literals):
echo {} -> '{}' - invalid as a brace expr.: at least 2 ,-separated tokens needed
This allows the use of unquoted {} with find, for instance.
echo {1..$to} -> '{1..<value-of-$to>}' - invalid as a brace expr. in bash: variables not supported; however, valid in ksh and zsh.
(fish, by contrast, expands any {...} sequence; similarly, zsh has option BRACE_CCL (OFF by default) for expanding individual characters inside {..}, which effectively causes expansion of any nonempty {...} sequence.)
The brace expansion is evaluated before the variables are expanded. You need a c-style for loop instead:
for ((i=1;i<=howmany;i++))
do
echo "Welcome"
done
create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done
The problem is that the "brace expansion" is performed before the "variable expansion"
for i in $(seq 1 $howmany)
works as #damienfrancois said, or, if you would like:
for i in $(eval echo "{$start..10}")
probably does, but don't use it for everyone's sanity.
You could also use a while loop:
while ((howmany--)); do
echo "Welcome"
done
We could also use eval in this case:
howmany=`grep -c $1 /root/file`
for i in $(eval echo {1..$howmany}); do
echo "Welcome"
done

Why is grep not finding integer*2?

For example
grep -n 'integer*2' *.f
Shows nothing.But
grep -n '*2' *.f
main.f:57: integer*2 itime(nxmax)
main.f:605: dxy=((xsource(is)-xobs)**2+(ysource(is)-yobs)**2)**.5
main.f:622: chisum=chisum+diff2/uobs**2
model.f:15: integer*2 veli(nxmax)
model.f:52: size2=size**2
time.f:151: integer*2 itime(nxmax)
I really do not understand this.
* is an operator, meaning "match the previous term 0 or more times". So integer*2 matches
intege2
integer2
integerr2
integerrr2
:
none of which appear in your program. * at the beginning of an RE is meaningless (there's no previous term), so is either ignored or treated as match for a *. Escape the * to have it match an actual star:
'integer\*2'
Your grep is using a regex. (Star is being interpreted differently than you might believe). Try
grep -F -n 'integer*2' *.f
Because grep is interpreting the search argument as a regular expression, in which * is meant as "zero or more of the preceding". So 'integer*2 would match intege2 as well as integerrrrr2 since * applies to the preceding r but will not match the literal integer*2.
Escape it with a backslash to interpret it as a literal * and you should get the desired matches:
grep -n 'integer\*2' *.f

Two pattern match on same sed command

I have the following sed command:
sed -n '/^out(/{n;p}' ${filename} | sed -n '/format/ s/.*format=//g; s/),$//gp; s/))$//gp'
I tried to do it as one line as in:
sed -n '/^out(/{n;}; /format/ s/.*format=//g; s/),$//gp; s/))$//gp' ${filename}
But that also display the lines I don't want (those that do not match).
What I have is a file with some strings as in:
entry(variable=value)),
format(variable=value)),
entry(variable=value)))
out(variable=value)),
format(variable=value)),
...
I just want the format lines that came right after the out entry. and remove those trailing )) or ),
You can use this sed command:
sed -nr '/^out[(]/ {n ; s/.*[(]([^)]+)[)].*/\1/p}' your_file
Once a out is found, it advanced to the next line (n) and uses the s command with p flag to extract only what is inside parenthesises.
Explanation:
I used [(] instead of \(. Outside brackets a ( usually means grouping, if you want a literal (, you need to escape it as \( or you can put it inside brackets. Most RE special characters dont need escaping when put inside brackets.
([^)]+) means a group (the "(" here are RE metacharacters not literal parenthesis) that consists of one or more (+) characters that are not (^) ) (literal closing parenthesis), the ^ inverts the character class [ ... ]

Resources