I am writing a script that takes a parameter with '~' delimiter and after splitting the string I want to insert the values into the array. I have quite a few posts on this problem and I am almost there but there a case where it fails. Here are the details.
myScript.sh
#!/bin/bash
tmpIFS=$IFS
IFS="~"
array=($1)
IFS=$tmpIFS
echo "${#array[#]}"
Executions
$ ./myScript.sh "A~B"
$ 2
Which is what I want. But when I do
$ ./myScript.sh "A~"
$ 1
I was expecting to have a array of size 2 again and the last cell to be an empty string.
How can I achieve that?
What I am trying to achieve in general with this script is to perform an http request using curl by passing 10 query parameters in it. Instead of having 10 parameters to my script I was thinking to get the data as one parameter delimited by a character in a specified order.
Thanks
The ~ are handled like white spaces in your expression, so the behavior makes sense with shell expansion rules. You can use the following workaround:
array=($1"")
To understand why this works, consider it with spaces (unchanged IFS): Assume $1 is A B C, then
array=($1)
expands to
array=(A B C )
which is an array of three elements. On the other hand,
array=($1"")
expands to
array=(A B C "")
which has four. If there is no space at the end of $1, this expands to
array=(A B C"")
which is the same as (A B C) again.
use ./script "A~ " then the last element is "empty".
Related
In my shell script there is a parameter that comes from certain systems and it gives an answer similar to this one: PAR0000008.
And I need to send only the last number of this parameter to another variable, ie VAR=8.
I used the command VAR=$( echo ${PAR} | cut -c 10 ) and it worked perfectly.
The problem is when the PAR parameter returns with numbers from two decimal places like PAR0000012. I need to discard the leading zeros and send only the number 12 to the variable, but I don't know how to do the logic in the Shell to discard all the characters to the left of the number.
Edit Using grep To Handle 0 As Part Of Final Number
Since you are using POSIX shell, making use of a utility like sed or grep (or cut) makes sense. grep is quite a bit more flexible in parsing the string allowing a REGEX match to handle the job. Say your variable v=PAR0312012 and you want the result r=312012. You can use a command substitution (e.g. $(...)) to parse the value assigning the result to r, e.g.
v=PAR0312012
r=$(echo $v | grep -Eo '[1-9].*$')
echo $r
The grep expression is:
-Eo - use Extended REGEX and only return matching portion of string,
[1-9].*$ - from the first character in [1-9] return the remainder of the string.
This will work for PAR0000012 or PAR0312012 (with result 312012).
Result
For PAR0312012
312012
Another Solution Using expr
If your variable can have zeros as part of the final number portion, then you must find the index where the first [1-9] character occurs, and then assign the substring beginning at that index to your result variable.
POSIX shell provides expr which provides a set of string parsing tools that can to this. The needed commands are:
expr index string charlist
and
expr substr string start end
Where start and end are the beginning and ending indexes to extract from the string. end just has to be long enough to encompass the entire substring, so you can just use the total length of your string, e.g.
v=PAR0312012
ndx=$(expr index "$v" "123456789")
r=$(expr substr "$v" "$ndx" 10)
echo $r
Result
312012
This will handle 0 anywhere after the first [1-9].
(note: the old expr ... isn't the fastest way of handling this, but if you are only concerned with a few tens of thousands of values, it will work fine. A billion numbers and another method will likely be needed)
This can be done easily using Parameter Expension.
var='PAR0000008'
echo "${var##*0}"
//prints 8
echo "${var##*[^1-9]}"
//prints 8
var="${var##*0}"
echo "$var"
//prints 8
var='PAR0000012'
echo "${var##*0}"
//prints 12
echo "${var##*[^1-9]}"
//prints 12
var="${var##*[^1-9]}"
echo "$var"
//prints 12
I have 2 variables, NUMS and TITLES.
NUMS contains the string
1
2
3
TITLES contains the string
A
B
C
How do I get output that looks like:
1 A
2 B
3 C
paste -d' ' <(echo "$NUMS") <(echo "$TITLES")
Having multi-line strings in variables suggests that you are probably doing something wrong. But you can try
paste -d ' ' <(echo "$nums") - <<<"$titles"
The basic syntax of paste is to read two or more file names; you can use a command substitution to replace a file anywhere, and you can use a here string or other redirection to receive one of the "files" on standard input (where the file name is then conventionally replaced with the pseudo-file -).
The default column separator from paste is a tab; you can replace it with a space or some other character with the -d option.
You should avoid upper case for your private variables; see also Correct Bash and shell script variable capitalization
Bash variables can contain even very long strings, but this is often clumsy and inefficient compared to reading straight from a file or pipeline.
Convert them to arrays, like this:
NUMS=($NUMS)
TITLES=($TITLES)
Then loop over indexes of whatever array, lets say NUMS like this:
for i in ${!NUMS[*]}; {
# and echo desired output
echo "${NUMS[$i]} ${TITLES[$i]}"
}
Awk alternative:
awk 'FNR==NR { map[FNR]=$0;next } { print map[FNR]" "$0} ' <(echo "$NUMS") <(echo "$TITLE")
For the first file/variable (NR==FNR), set up an array called map with the file number record as the index and the line as the value. Then for the second file, print the entry in the array as well as the line separated by a space.
I did not have to write any bash script before. Here is what I need to do.
My script will be run with a set of string arguments. Number of stings will be more than 8. I will have to concatenate strings 9 and onward and make a single string from those. Like this...
myscript s1 s2 s3 s4 s5 s6 s7 s8 s9 s10....(total unknown)
in the script, I need to do this...
new string = s9 + s10 + ...
I am trying something like this...(from web search).
array="${#}"
tLen=${#array[#]}
# use for loop to read string beyond 9
for (( i=8; i<${tLen}; i++ ));
do
echo ${array[$i]} --> just to show string beyond 9
done
Not working. It prints out if i=0. Here is my input.
./tastest 1 2 3 4 5 6 7 8 A B C
I am expecting A B C to be printed. Finally I will have to make ABC.
Can anyone help?
It should be a lot simpler than the looping in the question:
shift 8
echo "$*"
Lose arguments 1-8; print all the other arguments as a single string with a single space separating arguments (and spaces within arguments preserved).
Or, if you need it in a variable, then:
nine_onwards="$*"
Or if you can't throw away the first 8 arguments in the main shell process:
nine_onwards="$(shift 8; echo "$*")"
You can check that there are at least 9 arguments, of course, complaining if there aren't. Or you can accept an empty string instead — with no error.
And if the arguments must be concatenated with no space (as in the amendment to the question), then you have to juggle with $IFS:
nine_onwards="$(shift 8; IFS=""; echo "$*")"
If I'm interpreting the comments from below this answer correctly, then you want to save the first 8 arguments in 8 separate simple (non-array) variables, and then arguments 9 onwards in another simple variable with no spaces between the argument values.
That's trivially doable:
var1="$1"
var2="$2"
var3="$3"
var4="$4"
var5="$5"
var6="$6"
var7="$7"
var8="$8"
var9="$(shift 8; IFS=""; echo "$*")"
The names don't have to be as closely related as those are. You could use:
teflon="$1"
absinthe="$2"
astronomy="$3"
lobster="$4"
darkest_peru="$5"
mp="$6"
culinary="$7"
dogma="$8"
concatenation="$(shift 8; IFS=""; echo "$*")"
You don't have to do them in that order, either; any sequence (permutation) will do nicely.
Note, too, that in the question, you have:
array="${#}"
Despite the name, that creates a simple variable containing the arguments. To create an array, you must use parentheses like this, where the spaces are optional:
array=( "$#" )
# Create a 0-index-based copy of the array of input arguments.
# (You could, however, work with the 1-based pseudo array $# directly.)
array=( "${#}" )
# Print a concatenation of all input arguments starting with the 9th
# (starting at 0-based index 8), which are passed *individually* to
# `printf`, due to use of `#` to reference the array [slice]
# `%s` as the `printf` format then joins the elements with no separator
# (and no trailing \n).
printf '%s' "${array[#]:8}"
# Alternative: Print the elements separated with a space:
# Note that using `*` instead of `#` causes the array [slice] to be expanded
# to a *single* string using the first char. in `$IFS` as the separator,
# which is a space by default; here you could add a trailing \n by using
# '%s\n' as the `printf` format string.
printf '%s' "${array[*]:8}"
Note that array="${#}" does not create an array - it simply creates a string scalar comprising the concatenation of the input array's elements (invariably) separated by a space each; to create an array, you must enclose it in (...).
To create a space-separated single string from the arguments starting with the 9th enclosed in double quotes, as you request in your follow-up question, use the following:
printf -v var10 '"%s"' "${array[*]:8}"
With the last sample call from your question $var10 will then contain literal "A B C", including the double quotes.
As for assigning arguments 1 through 8 to individual variables.:
Jonathan Leffler's helpful answer shows how to save the first 8 arguments in individual variables.
Here's an algorithmic alternative that creates individual variables based on a given name prefix and sequence number:
n=8 # how many arguments to assign to individual variables
# Create n 'var<i>' variables capturing the first n arguments.
i=0 # variable sequence number
for val in "${array[#]:0:n}"; do
declare "var$((++i))=$val" # create $var<i>, starting with index 1
done
# Print the variables created and their values, using variable indirection.
printf "\nvar<i> variables:\n"
for varName in "${!var#}"; do
printf '%s\n' "$varName=${!varName}"
done
You are close - something like this would work:
array=( ${*} )
# use for loop to read string beyond 9
for (( i=8; i<${#array[*]}; i++ ));
do
echo -n ${array[$i]}
done
I'm trying to split a string into individual characters.
For example temp="hello" into "h", "e", "l", "l", "o"
I tried using IFS because that's what I used in previous string splits and wanted to keep the consistency across the script.
IFS='' read h e l l o <<<"$temp" does not work. What am I doing wrong?
You can use fold:
arr=($(fold -w1 <<< "$temp"))
Verify:
declare -p arr
declare -a arr='([0]="h" [1]="e" [2]="l" [3]="l" [4]="o")'
TL;DR: this is just to see if it can be done. Use fold like anubhava suggests; starting a single process is a small price to pay to avoid not one, but two uses of eval.
I wouldn't actually use this (I think it's safe, but I wouldn't swear to it, and boy, is it ugly!), but you can use eval, brace expansion, and substring parameter expansion to accomplish this.
$ temp=hello
$ arr=( $(eval echo $(eval echo \\\${temp:{0..${#temp}}:1})) )
$ printf '%s\n' "${arr[#]}"
h
e
l
l
o
How does this work? Very delicately. First, the shell expands ${#temp} to the length of the variable whose contents we want to split. Next, the inner eval turns the string \${temp:{0..5}:1} into a set of strings ${temp:0:1}, ${temp:1:1}, etc. The outer eval then performs the parameter expansions that produce one letter each from temp, and those letters provide the contents of the array.
In Python, if I do this:
print "4" * 4
I get
> "4444"
In Perl, I'd get
> 16
Is there an easy way to do the former in Perl?
$ perl -e 'print "4" x 4; print "\n"'
4444
The x operator is documented in perldoc perlop. Here binary means an operator taking two arguments, not composed of bits, by the way.
Binary "x" is the repetition operator. In scalar context or if the
left operand is not enclosed in parentheses, it returns a string consisting
of the left operand repeated the number of times specified by the right
operand. In list context, if the left operand is enclosed in parentheses
or is a list formed by "qw/STRING/", it repeats the list. If the right
operand is zero or negative, it returns an empty string or an empty
list, depending on the context.
print '-' x 80; # Print row of dashes
print "\t" x ($tab/8), ' ' x ($tab%8); # Tab over
#ones = (1) x 80; # A list of 80 1’s
#ones = (5) x #ones; # Set all elements to 5
perl -e is meant to execute Perl code from the command line:
$ perl --help
Usage: perl [switches] [--] [programfile] [arguments]
-e program one line of program (several -e's allowed, omit programfile)
In Perl, you want to use the "x" operator.
Note the difference between
"4" x 4
and
("4") x 4
The former produces a repeated string:
"4444"
the latter a repeated list:
("4", "4", "4", "4")
It's very similar in Perl
print "4" x 4;
FWIW, it’s also print 4 x 4 in Perl.
In general, in Perl, operators are monomorphic, ie. you have different sets of operators for string semantics, for numeric semantics, for bitwise semantics, etc., where it makes sense, and the type of the operands largely doesn’t matter. When you apply a numeric operator to a string, the string is converted to a number first and you get the operation you asked for (eg. multiplication), and when you apply a string operator to a number, it’s turned into a string and you get the operation you asked for (eg. repetition). Perl pays attention to the operator first and the types of the operands only second – if indeed it pays them any mind at all.
This is the opposite of Python and most other languages, where you use one set of operators, and the types of the operands determine which semantics you’ll actually get – ie. operators are polymorphic.
If you want to print 10 character "A"s, you can also do this
perl -e 'print "A" x 10'; echo
Example with output
user#linux:~$ perl -e 'print "A" x 10'; echo
AAAAAAAAAA
user#linux:~$
All answers, given so far, missed mentioning that the operator x does not only work on string literals, but also on variables that are strings or expressions that evaluate to strings like
use feature 'say';
my $msg = "hello ";
say $msg x 2;
say chr(33) x 3;
like this
hello hello
!!!
and, even more important, x does an automatic conversion of expressions into strings if they aren't already (thanks to ggorlen for pointing me into that direction!). So for example
say 4 x 2;
say [$msg] x 2;
will result in something like the following as output
44
ARRAY(0x30ca10)ARRAY(0x30ca10)
Came this way looking for an answer. Didn't quite find what I was looking for so I thought I'd share my learning. I wanted to compose dynamic SQL CRUD statements with the appropriate number of placeholders.
$table = "ORDERS";
#fields = ("ORDER_ID", "SALESMAN_ID", "CUSTOMER_ID", "ORDER_DATE", "STATUS");
$sql = "INSERT INTO $table (" . join(',', #fields) . ') VALUES (' . '?,' x (#fields - 1) . '?)';
print $sql;
The output looks like this...
INSERT INTO ORDERS (ORDER_ID,SALESMAN_ID,CUSTOMER_ID,ORDER_DATE,STATUS) VALUES (?,?,?,?,?)