Error in string Concatenation in Shell Scripting - shell

I am beginner to Shell scripting.
I have used a variable to store value A="MyScript". I tried to concatenate the string in subsequent steps $A_new. To my surprise it didn't work and $A.new worked.
Could you please help me in understanding these details?
Thanks

Shell variable names are composed of alphabetic characters, numbers and underscores.
3.231 Name
In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.
So when you wrote $A_new the shell interpreted the underscore (and new) as part of the variable name and expanded the variable A_new.
A period is not valid in a variable name so when the shell parsed $A.new for a variable to expand it stopped at the period and expanded the A variable.
The ${A} syntax is designed to allow this to work as intended here.
You can use any of the following to have this work correctly (in rough order of preferability):
echo "${A}_new"
echo "$A"_new
echo $A\_new
The last is least desirable because you can't quote the whole string (or the \ doesn't get removed. So since you should basically always quote your variable expansions you would end up probably doing echo "$A"\_new but that's no different then point 2 ultimately so why bother.

This happens because the underscore is the valid character in variable names.
Try this way:
${A}_new or "$A"_new

The name of a variable can contain letters ( a to z or A to Z), numbers ( 0 to 9) or the underscore character ( _).
Shell does not require any variable declaration as in programming languages as C , C++ or java. So when you write $A_new shell consider A_new as a variable, which you have not assigned any value therefore it comes to be null.
To achieve what you mentioned use as :
${A}_new
Its always a good practice to enclose variable names in braces after $ sign to avoid such situation.

Related

Why can substring variable expansion reference a variable without a dollar sign?

In bash, to get the first 4 characters of a variable, you can do:
variable='this is a variable'
echo ${variable:0:4}
Instead of hard-coding the length, you can reference a variable like this:
length=4
echo ${variable:0:$length}
However, it seems that you can leave off the $ off length as well:
echo ${variable:0:length}
It does not make sense to me that you should be able to do this because I always thought that to use/evaluate a variable, you have to prefix it with $.
In other languages, I would expect the text after each : to be a number or an expression that evaluates to a number. And in bash, length wouldn't evaluate to anything, but $length would.
This is confusing. Could someone help me understand what is going on here?
In general is correct to use the "$" symbol to expand a variable, but in some cases the bash auto-expands variable. For example in context like arithmetics or indirect expansion
(see Shell expansion to more detailed information).
However your case is a simple arithmetic context expansion.

Bash - Why does $VAR1=FOO or 'VAR=FOO' (with quotes) return command not found?

For each of two examples below I'll try to explain what result I expected and what I got instead. I'm hoping for you to help me understand why I was wrong.
1)
VAR1=VAR2
$VAR1=FOO
result: -bash: VAR2=FOO: command not found
In the second line, $VAR1 gets expanded to VAR2, but why does Bash interpret the resulting VAR2=FOO as a command name rather than a variable assignment?
2)
'VAR=FOO'
result: -bash: VAR=FOO: command not found
Why do the quotes make Bash treat the variable assignment as a command name?
Could you please describe, step by step, how Bash processes my two examples?
How best to indirectly assign variables is adequately answered in other Q&A entries in this knowledgebase. Among those:
Indirect variable assignment in bash
Saving function output into a variable named in an argument
If that's what you actually intend to ask, then this question should be closed as a duplicate. I'm going to make a contrary assumption and focus on the literal question -- why your other approaches failed -- below.
What does the POSIX sh language specify as a valid assignment? Why does $var1=foo or 'var=foo' fail?
Background: On the POSIX sh specification
The POSIX shell command language specification is very specific about what constitutes an assignment, as quoted below:
4.21 Variable Assignment
In the shell command language, a word consisting of the following parts:
varname=value
When used in a context where assignment is defined to occur and at no other time, the value (representing a word or field) shall be assigned as the value of the variable denoted by varname.
The varname and value parts shall meet the requirements for a name and a word, respectively, except that they are delimited by the embedded unquoted equals-sign, in addition to other delimiters.
Also, from section 2.9.1, on Simple Commands, with emphasis added:
The words that are recognized as variable assignments or redirections according to Shell Grammar Rules are saved for processing in steps 3 and 4.
The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.
Redirections shall be performed as described in Redirection.
Each variable assignment shall be expanded for tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal prior to assigning the value.
Also, from the grammar:
If all the characters preceding '=' form a valid name (see the Base Definitions volume of IEEE Std 1003.1-2001, Section 3.230, Name), the token ASSIGNMENT_WORD shall be returned. (Quoted characters cannot participate in forming a valid name.)
Note from this:
The command must be recognized as an assignment at the very beginning of the parsing sequence, before any expansions (or quote removal!) have taken place.
The name must be a valid name. Literal quotes are not part of a valid variable name.
The equals sign must be unquoted. In your second example, the entire string was quoted.
Assignments are recognized before tilde expansion, parameter expansion, command substitution, etc.
Why $var1=foo fails to act as an assignment
As given in the grammar, all characters before the = in an assignment must be valid characters within a variable name for an assignment to be recognized. $ is not a valid character in a name. Because assignments are recognized in step 1 of simple command processing, before expansion takes place, the literal text $var1, not the value of that variable, is used for this matching.
Why 'var=foo' fails to act as an assignment
First, all characters before the = must be valid in variable names, and ' is not valid in a variable name.
Second, an assignment is only recognized if the = is not quoted.
1)
VAR1=VAR2
$VAR1=FOO
You want to use a variable name contained in a variable for the assignment. Bash syntax does not allow this. However, there is an easy workaround :
VAR1=VAR2
declare "$VAR1"=FOO
It works with local and export too.
2)
By using single quotes (double quotes would yield the same result), you are telling Bash that what is inside is a string and to treat it as a single entity. Since it is the first item on the line, Bash tries to find an alias, or shell builtin, or an executable file in its PATH, that would be named VAR=FOO. Not finding it, it tells you there is no such command.
An assignment is not a normal command. To perform an assignment contained in a quote, you would need to use eval, like so :
eval "$VAR1=FOO" # But please don't do that in real life
Most experienced bash programmers would probably tell you to avoid eval, as it has serious drawbacks, and I am giving it as an example just to recommend against its use : while in the example above it would not involve any security risk or error potential because the value of VAR1 is known and safe, there are many cases where an arbitrary (i.e. user-supplied) value could cause a crash or unexpected behavior. Quoting inside an eval statement is also more difficult and reduces readability.
You declare VAR2 earlier in the program, right?
If you are trying to assign the value of VAR2 to VAR1, then you need to make sure and use $ in front of VAR2, like so:
VAR1=$VAR2
That will set the value of VAR2 equal to VAR1, because when you utilize the $, you are saying that value that is stored in the variable. Otherwise it doesn't recognize it as a variable.
Basically, a variable that doesn't have a $ in front of it will be interpreted as a command. Any word will. That's why we have the $ to clarify "hey this is a variable".

How to create a variable with (-) hyphen as one of the character in it in shell scripting

I have a requirement of using a variable as abc-def which i am passing as a parameter and want to use in the shell script.
ex:
#!/bin/bash
abc-def="xyz"
echo "$abc-def"
there is an hyphen in the variable, i will have to use abc-def as a parameter and script needs to understand it wherever i will use.
You don't.
Variable names used by the utilities in the Shell and Utilities volume of IEEE Std 1003.1-2001 consist solely of upper and lowercase letters, digits, and the '_' (underscore) from the characters defined in Portable Character Set and do not begin with a digit. Other characters may be permitted by an implementation; applications shall tolerate the presence of such names.
With a suitable recent bash, you can create an associative array, and use the "variable" name as an array key:
#!/bin/bash
declare -A vars
name="abc-def"
value=xyz
vars["$name"]=$value
echo "${vars["$name"]}"

Should awk expand escape sequences in command-line assigned variables?

I've recently discovered that Awk's -v VAR=VAL syntax for initializing variables on the command line expands escape sequences in VAL. I previously thought that it was a good way to pass strings into Awk without needing to run an escaping function over them first.
For example, the following script:
awk -v VAR='x\tx' 'BEGIN{printf("%s\n", VAR);}'
I would expect to print
x\tx
but actually prints:
x x
An aside: environment variables to pass strings in unmodified instead, this question isn't asking how to get the behaviour I previously expected.
Here's what the man page has to say on the matter:
-v var=val, --assign var=val Assign the value val to the variable var, before execution of the program begins. Such variable values are available to the
BEGIN block of an AWK program.
And further down:
String Constants
String constants in AWK are sequences of characters enclosed between double quotes (like "value"). Within strings, certain escape
sequences are recognized, as in C. These are:
... list of escape seqeuences ...
The escape sequences may also be used inside constant regular expressions (e.g., /[ \t\f\n\r\v]/ matches whitespace characters).
In compatibility mode, the characters represented by octal and hexadecimal escape sequences are treated literally when used in
regular expression constants. Thus, /a\52b/ is equivalent to /a*b/.
The way I read this, val in -v var=val is not a string constant, and there is no text to indicate that the string constant escaping rules apply.
My questions:
Is there a more authoritative source for the awk language than the man page, and if so what does it specify?
What does POSIX have to say about this, if anything?
Do all versions of Awk behave this way, i.e. can I rely on the expansion being done if I actually want it?
The assignment is a string constant.
The relevant sections from the standard are:
-v assignment
The application shall ensure that the assignment argument is in the same form as an assignment operand. The specified variable assignment shall occur prior to executing the awk program, including the actions associated with BEGIN patterns (if any). Multiple occurrences of this option can be specified.
and
An operand that begins with an underscore or alphabetic character from the portable character set (see the table in XBD Portable Character Set ), followed by a sequence of underscores, digits, and alphabetics from the portable character set, followed by the '=' character, shall specify a variable assignment rather than a pathname. The characters before the '=' represent the name of an awk variable; if that name is an awk reserved word (see Grammar ) the behavior is undefined. The characters following the <equals-sign> shall be interpreted as if they appeared in the awk program preceded and followed by a double-quote ( ' )' character, as a STRING token (see Grammar ), except that if the last character is an unescaped , it shall be interpreted as a literal rather than as the first character of the sequence "\""

Bash bad substitution with subshell and substring

A contrived example... given
FOO="/foo/bar/baz"
this works (in bash)
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
this doesn't
BAZ=${$(basename $FOO):0:1} # result is bad substitution
My question is which rule causes this [subshell substitution] to evaluate incorrectly? And what is the correct way, if any, to do this in 1 hop?
First off, note that when you say this:
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
the first bit in the construct for BAZ is BAR and not the value that you want to take the first character of. So even if bash allowed variable names to contain arbitrary characters your result in the second expression wouldn't be what you want.
However, as to the rule that's preventing this, allow me to quote from the bash man page:
DEFINITIONS
The following definitions are used throughout the rest of this docu‐
ment.
blank A space or tab.
word A sequence of characters considered as a single unit by the
shell. Also known as a token.
name A word consisting only of alphanumeric characters and under‐
scores, and beginning with an alphabetic character or an under‐
score. Also referred to as an identifier.
Then a bit later:
PARAMETERS
A parameter is an entity that stores values. It can be a name, a num‐
ber, or one of the special characters listed below under Special Param‐
eters. A variable is a parameter denoted by a name. A variable has a
value and zero or more attributes. Attributes are assigned using the
declare builtin command (see declare below in SHELL BUILTIN COMMANDS).
And later when it defines the syntax you're asking about:
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of
parameter starting at the character specified by offset.
So the rules as articulated in the manpage say that the ${foo:x:y} construct must have a parameter as the first part, and that a parameter can only be a name, a number, or one of the few special parameter characters. $(basename $FOO) is not one of the allowed possibilities for a parameter.
As for a way to do this in one assignment, use a pipe to other commands as mentioned in other responses.
Modified forms of parameter substitution such as ${parameter#word} can only modify a parameter, not an arbitrary word.
In this case, you might pipe the output of basename to a dd command, like
BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)
(If you want a higher count, increase count and not bs, otherwise you may get fewer bytes than requested.)
In the general case, there is no way to do things like this in one assignment.
It fails because ${BAR:0:1} is a variable expansion. Bash expects to see a variable name after ${, not a value.
I'm not aware of a way to do it in a single expression.
As others have said, the first parameter of ${} needs to be a variable name. But you can use another subshell to approximate what you're trying to do.
Instead of:
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Use:
BAZ=$(_TMP=$(basename $FOO); echo ${_TMP:0:1}) # this works
A contrived solution for your contrived example:
BAZ=$(expr $(basename $FOO) : '\(.\)')
as in
$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : '\(.\)')
$ echo $BAZ
j
${string:0:1},string must be a variable name
for example:
FOO="/foo/bar/baz"
baz="foo"
BAZ=eval echo '${'"$(basename $FOO)"':0:1}'
echo $BAZ
the result is 'f'

Resources