If you append ^ to a variable, Bash capitalises the first letter of its contents. (Similarly, , sends it to lowercase and doubling-up either of these applies the transformation to the whole string, rather than just the first letter.)
foo="hello world"
echo ${foo^} # Hello world
You can also do ${variable:position:length} to extract a substring:
echo ${foo:0:1} # h
So far, I haven't found a way to combine these without, obviously, creating a temporary variable. Is there a form where I can get just the capitalised first letter out of an arbitrary string?
It does not change the basic limitation you are seeing in terms of not being able to "chain" expansions, but you can assign the result of an expansion to the same variable and do away with the temporary variable.
For instance:
A=text
A="${A^}"
A="${A//x/s}"
echo "$A"
echoes "Test".
No. Parameter expansion operators do not compose, so if you want more than one side effect, you need a temporary variable (which can include overwriting the original value as shown by #fred) or an external tool to process the result of the expansion (as shown by #anubhava).
Your other alternative is to use a different shell that does support more complicated operations, like zsh:
% foo="hello world"
% % print ${(U)${foo:0:1}}
H
You can use tr with substring:
tr [[:lower:]] [[:upper:]] <<< "${foo:0:1}"
H
Related
I'm trying to create function that will print message bound to variable in certain color. The message variable is passed as argument of this function. The problem is that I'm getting only text up to first space (only first word of message). My script looks like this:
#!/usr/bash
lbGREEN='\e[1;92m'
NC='\e[0m'
normalMessage="Everything fine"
echo_message() {
echo -e ${lbGREEN}$1${NC}
}
echo_message $normalMessage
My output is:
Everything
As Inian pointed out in comments, your problem is unquoted variable expansion
echo_message $normalMessage
becomes
echo_message Everything fine
once the variable expands, meaning that each word of your input string is getting read in as a separate argument. When this happens $1=Everything and $2=fine.
This is fixed by double-quoting your variable, which allows expansion, but will mean the result of the expansion will still be read as one argument.
echo_message "$normalMessage"
becomes
echo_message "Everything fine"
Like this $1=Everything fine
In the future, I recommend using https://www.shellcheck.net/, or the CLI version of shellcheck, it will highlight all kinds of common bash gotcha's, included unquoted expansion.
For me, I had to change the header for "#!/bin/bash", but apparently that is not the problem for you.
In your echo you are printing only the first word with the $1, if you change it to $2 you will print the second word (parameter) and so on.
You can pass the name inside quotes or print all the parameters with $#
Solution 1 (with $#):
lbGREEN='\e[1;92m'
NC='\e[0m'
normalMessage="Everything fine"
echo_message() {
echo -e ${lbGREEN}$#${NC}
}
echo_message $normalMessage
Solution 2 (with quotes):
lbGREEN='\e[1;92m'
NC='\e[0m'
normalMessage="Everything fine"
echo_message() {
echo -e ${lbGREEN}$1${NC}
}
echo_message "$normalMessage"
You should get a look to https://stackoverflow.com/a/6212408/1428602
IMHO, $1 only return the 1st word, so you have to use a loop or try with $*
You've got the quoting wrong.
If you want to simulate the behaviour of echo, your function should accept multiple parameters, and print them all. Currently it's only evaluating the first parameter, so I suggest using $* instead. You also need to enclose the argument in double quotes to protect any special characters:
echo_message() {
echo -e "${lbGREEN}$*${NC}"
}
The special variable $* expands to all the arguments, separated by spaces (or more accurately, the first character of $IFS, which is usually a space character). Note that you almost always want "$#" instead of "$*", and this is one of the rare occasions where the latter is also correct, though with slightly different semantics if IFS is set to a non-standard value.
Now the function supports multiple arguments, and prints them all in green, separated by spaces. However, I would recommend that you also quote the argument when calling the function:
echo_message "$normalMessage"
While spaces in $normalMessage will now be treated correctly, other special characters like ! will still require the quotes.
I've been trying to figure this out, unfortunately I can't. I am trying to create a function that finds the ';' character, puts four spaces before it and then and puts the code back together in a neat sentence. I've been cracking at this for a bit, and can't figure out a couple of things. I can't get the output to display what I want it to. I've tried finding the index of the ';' character and it seems I'm going about it the wrong way. The other mistake that I seem to be making is that I'm trying to split in a array in a for loop, and then split the individual words in the array by letter but I can't figure out how to do that either. If someone can give me a pointer this would be greatly appreciated. This is in bash version 4.3.48
#!commentPlacer()
{
arg=($1) #argument
len=${#arg[#]} #length of the argument
comment=; #character to look for in second loop
commaIndex=(${arg[#]#;}) #the attempted index look up
commentSpace=" ;" #the variable being concatenated into the array
for(( count1=0; count1 <= ${#arg[#]}; count1++ )) #search the argument looking for comment space
do if [[ ${arg[count1]} != commentSpace ]] #if no commentSpace variable then
then for (( count2=0; count2 < ${#arg[count1]} ; count2++ )) #loop through again
do if [[ ${arg[count2]} != comment ]] #if no comment
then A=(${arg[#]:0:commaIndex})
A+=(commentSpace)
A+=(${arg[#]commaIndex:-1}) #concatenate array
echo "$A"
fi
done
fi
done
}
If I understand what you want correctly, it's basically to put 4 spaces in front of each ";" in the argument, and print the result. This is actually simple to do in bash with a string substitution:
commentPlacer() {
echo "${1//;/ ;}"
}
The expansion here has the format ${variable//pattern/replacement}, and it gives the contents of the variable, with each occurrence of pattern replaced by replacement. Note that with only a single / before the pattern, it would replace only the first occurrence.
Now, I'm not sure I understand how your script is supposed to work, but I see several things that clearly aren't doing what you expect them to do. Here's a quick summary of the problems I see:
arg=($1) #argument
This doesn't create an array of characters from the first argument. var=(...) treats the thing in ( ) as a list of words, not characters. Since $1 isn't in double-quotes, it'll be split into words based on whitespace (generally spaces, tabs, and linefeeds), and then any of those words that contain wildcards will be expanded to a list of matching filenames. I'm pretty sure this isn't at all what you want (in fact, it's almost never what you want, so variable references should almost always be double-quoted to prevent it). Creating a character array in bash isn't easy, and in general isn't something you want to do. You can access individual characters in a string variable with ${var:index:1}, where index is the character you want (counting from 0).
commaIndex=(${arg[#]#;}) #the attempted index look up
This doesn't do a lookup. The substitution ${var#pattern} gives the value of var with pattern removed from the front (if it matches). If there are multiple possible matches, it uses the shortest one. The variant ${var##pattern} uses the longest possible match. With ${array[#]#pattern}, it'll try to remove the pattern from each element -- and since it's not in double-quotes, the result of that gets word-split and wildcard-expanded as usual. I'm pretty sure this isn't at all what you want.
if [[ ${arg[count1]} != commentSpace ]] #if no commentSpace variable then
Here (and in a number of other places), you're using a variable without $ in front; this doesn't use the variable at all, it just treats "commentSpace" as a static string. Also, in several places it's important to have double-quotes around it, e.g. to keep the spaces in $commentSpace from vanishing due to word splitting. There are some places where it's safe to leave the double-quotes off, but in general it's too hard to keep track of them, so just use double-quotes everywhere.
General suggestions: don't try to write c (or java or whatever) programs in bash; it works too differently, and you have to think differently. Use shellcheck.net to spot common problems (like non-double-quoted variable references). Finally, you can see what bash is doing by putting set -x before a section that doesn't do what you expect; that'll make bash print each line as it executes it, showing the equivalent of what it's executing.
Make a little function using pattern substitution on stdin:
semicolon4s() { while read x; do echo "${x//;/ ;}"; done; }
semicolon4s <<< 'foo;bar;baz'
Output:
foo ;bar ;baz
I need to be able to expand a variable into exactly what is it declaration string is rather than bash expanding other variables in its declaration like it normally does.
variable=word
var="this variable contains a $variable"
echo ????
I need a command that results in: "this variable contains a $variable"
...rather than: "this variable contains a word"
The variable declaration must remain the same because it needs to expand normally in all other situations. So I can't use single quotes in the declaration.
What you're asking for is effectively impossible, because inside double quotes, $variable is substituted by the contents of that variable at the time of definition.
At the time you're printing it, the expansion has long happened (as can be seen using set).
If you cannot change the definition at all, the only thing you can try is to revert the substitution:
echo ${var/"$variable"/'$variable'}
This will replace every occurrence of what $variable currently holds by the string literal $variable, so in your example it would replace all occurrences of word with $variable, regardless of whether it was defined that way or not.
Also, as Jasper correctly pointed out, this relies on the fact that $variable does not change between the definition and the printing of $var.
Now, if you actually can change the definition, but only want the expansion to still happen, there are a number of options:
Use single quotes in the definition, and eval when printing.
var='this variable contains a $variable'
# later
eval "echo "'"'"$var"'"' # -> this variable contains a word
echo $var # -> this variable contains a $variable
Admittedly, this is rather cumbersome.
Also note that variable expansion happens at the time of printing rather than at the time of definition (actually, just at whatever time eval run).
Use two different variables.
Instead of writing eval "echo "'"'"$var"'"' every time, you could just store its result in a variable and use that, right?
var='this variable contains a $variable'
varX="$(eval "echo "'"'"$var"'"')"
# later
echo $varX # -> this variable contains a word
echo $var # -> this variable contains a $variable
Or, of course just copy the definition of $var and replace ' by "... but that's boring. :P
Here the expansion happens at the time of defining $varX.
Use an array instead of two different variables.
This is really interesting, because for arrays $var is equal to ${var[0]}, so you can do
tmp='this variable contains a $variable'
var=("$(eval "echo "'"'"$tmp"'"')" "$tmp")
# later
echo $var # -> this variable contains a word
echo ${var[1]} # -> this variable contains a $variable
This is probably as close as it gets to what you want, but you will still have to change the definition.
you can use single quote:
variable=word
var='this variable contains a $variable'
echo var
Also you can use sed
variable=word
var="this variable contains a $variable"
var=$(echo "${var}" | sed "s/\${variable}/${variable}/g")
I wrote a little script in bash, but it only worked when I stored the string as a variable, and I'd like to know why. Here's the summary:
When I use the string itself, bash treats it as a single entity
for word in "this is a sentence"; do
echo $word
done
# => this is a sentence
If I save the exact same string into a variable, bash iterates over the words
sentence="this is a sentence"
for word in $sentence; do
echo $word
done
# => this
# is
# a
# sentence
Why are these being treated differently?
Is there a simple way to iterate through the words in the string without first saving the string as a variable?
The quotes tell bash to treat a thing in quotes as a single parameter in a parameter list at the time the expression is evaluated. The quotes (unless protected with \ or ') are removed.
echo "" # prints newlines, no quotes
echo '""' # Print ""
export X='""'
env | grep X # X contains ""
export X=""
env | grep X # X is empty
When you use a variable, bash unpacks it as is (i.e. as if you typed the variable's contents in the variable's place). For a for-loop bash determines the list-elements to iterate over by separating the for-loop's parameters by whitespace, but treating (as always) quote-protected items a single parameter/list-element. Your variable contained no quotes -- items are treated as separate parameters.
As comments suggested, quotes are important. A for loop will step through a list of values terminated by a semicolon, and that list is a set of strings. Unquoted strings are delimited usually by whitespace. Whitespace inside a quoted string does not separate the string from its brethren, it's simply part of the quoted string. There's some truly excellent documentation about quotes in bash at http://mywiki.wooledge.org/Quotes . Read it. Read it now. You'll find a part that says
The quotes are not actually passed along to the command. They are removed by the shell (this process is cleverly called "quote removal").
To step through the words in a sentence that's stored in a variable (if I've inferred your question correctly), you could perhaps use an array to separate the words by whitespace:
#!/bin/bash
sentence="this is a sentence"
IFS=" " read -a words <<< "$sentence"
for word in "${words[#]}"; do
echo "$word"
done
In bash, read -a will divide a string by $IFS and place the divided parts into elements of the array. See http://mywiki.wooledge.org/BashGuide/Arrays for more information about how bash arrays work.
If you want more details in pursuit of a specific problem, you might want to tell us what the problem is, or risk making this an XY problem.
In the assignment
sentence="this is a sentence"
there are no unquoted spaces, so everything to the right of the = is treated as a single word. (Something like sentence=this is a sentence would be parsed as a single assignment sentence=this followed by an attempt to run a program called is.) As a result, the value of sentences is a sequence of 18 characters. It is identical to
sentence=this\ is\ a\ sentence
because again, there are no unquoted spaces.
For the same reason
for word in "this is a sentence"; do
echo $word
done
has word being set to each word in the following sequence, which only contains a single word because there are no unquoted spaces.
The key difference with your other loop is that parameter expansions are subject to word-splitting after the fact. The loop
for word in $sentence; do
echo $word
done
after parameter expansion looks like
for word in this is a sentence; do
echo $word
done
so now word is set to each of the 4 words in the list following the in keyword.
It's not clear what you are actually asking at the end of your question, but the preceding is legal code. There is no requirement that a string be placed in quotes in bash; quotes do not define something as a string value, but simply escape every character that appears within the quotes. "foo" and \f\o\o are the same thing in shell.
Quoting turns any string into a single unit. If you lose the quotes, everything should be fine.
I'm sure there's a really easy way of doing this. I'm trying to take a file which contains some environment variables and expand it so that those which are known are expanded to their values whereas those which are not are left alone.
For example, if my file contained the following:
${I_EXIST}
${I_ALSO_EXIST}
${I_DONT_EXIST}
this would be expanded to:
existValue
alsoExistValue
${I_DONT_EXIST}
I ideally want to do this as simply as possible so I don't want a complex substitution using sed, awk or perl. I'm thinking of something similar to a "Here" file, but apart from the fact that I can't get the syntax right, it also blanks out anything which does not expand. E.g:
cat <<EOF
> ${I_EXIST}
> ${I_ALSO_EXIST}
> ${I_DONT_EXIST}
EOF
existValue
alsoExistValue
(i.e. the last value expands to nothing)
Update
Should really have made clear that I was thinking about potentially more than one substitution per line. One way I did find to do this, if we're not fussed about the variables appearing in the file as ${MYVAR} but maybe MYVAR will do:
m4 $( env | sed 's/\([A-Za-z0-9]*\)=\([\/A-Za-z_0-9:|%*. -#]*\)/-D\1=\2' ) myfile
This uses the M4 preprocessor to substitute all the pairs in your environment. A couple of caviats here:
Sorry about the reg exp stuff. It looks pretty nasty and I'm sure there are nicer ways of expressing this. I found problems if my env vars had spaces in them or any unusual characters that weren't in the set.
Of course this is a blunt substitution tool (which I was trying to avoid) so variable might get substituted when you didn't want it to happen.
#!/bin/bash
while read a;
do
n=$(eval echo $a)
if [[ "$n" == "" ]]
then
echo $a
else
echo $n
fi
done < input
Using this as input
${HOME}
${nonexistent}
Gives
/home/myuser
${nonexistent}
Easy to read? Maybe not. It is short and works though :-)
while read r; do
echo $(eval echo ${r%\}}:-'$r'\})
done < input
Magic used:
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion
Edit: Further explanation, I hope it makes some sense.
We use two techniques; from the above docs:
${parameter:−word} If parameter is
unset or null, the expansion of word
is substituted. Otherwise, the value
of parameter is substituted.
And
${parameter%word}
The word is expanded to produce a
pattern just as in filename expansion.
If the pattern matches a trailing
portion of the expanded value of
parameter, then the result of the
expansion is the value of parameter
with the shortest matching pattern
(the ‘%’ case) or the longest matching
pattern (the ‘%%’ case) deleted. ...
We use the fact that the input is just what we can use in the shell, we have ${FOOBAR} but need ${FOOBAR:-'${FOOBAR}'} (Single quotes to avoid expansion).
# echo ${doesntexist:-Hello}
Hello
# doesexist=World
# echo ${doesexist:-Will not be printed}
World
So what we need to inject is :-'${FOOBAR}'
To achieve this we trim the } at the end, add the string, then put another } back afterwards.
# echo $r
${FOOBAR}
# echo ${r%\}}
${FOOBAR
The final \} isn't really necessary, since it's got no beginning in this case, but it's better to be explicit and escape it. (Much like you would escape echo \* even if echo * without any matching files gives you a literal *).
Edit2: This of course doesn't take into account that you wanted to support multiple variables in a single row; or any rows with other stuff in them.
while read name; do echo "$name = " $(eval echo $name); done < file_with_vars.txt
will echo all variables what know.
e.g.
in my file called vv
${PATH}
${HAVENOT}
${LOCALE}
will print
${PATH} = /usr/local/narwhal/bin:/opt/local/bin:/opt/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:~/bin
${HAVENOT} =
${LOCALE} = UTF-8
modify the output format as you wish :)