splitting a string into individual characters in bash using IFS - string

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.

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

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

How to get value from command line using for loop

Following is the code for extracting input from command line into bash script:
input=(*);
for i in {1..5..1}
do
input[i]=$($i);
done;
My question is: how to get $1, $2, $3, $4 values from input command line, where command line code input is:
bash script.sh "abc.txt" "|" "20" "yyyy-MM-dd"
Note: Not using for i in "${#}"
#!/bin/bash
for ((i=$#-1;i>=0;i--)); do
echo "${BASH_ARGV[$i]}"
done
Example: ./script.sh a "foo bar" c
Output:
a
foo bar
c
I don't know what you have against for i in "$#"; do..., but you can certainly do it with shift, for example:
while [ -n "$1" ]; do
printf " '%s'\n" "$1"
shift
done
Output
$ bash script.sh "abc.txt" "|" "20" "yyyy-MM-dd"
'abc.txt'
'|'
'20'
'yyyy-MM-dd'
Personally, I don't see why you exclude for i in "$#"; do ... it is a valid way to iterate though the args that will preserve quoted whitespace. You can also use the array and C-style for loop as indicated in the other answers.
note: if you are going to use your input array, you should use input=("$#") instead of input=($*). Using the latter will not preserve quoted whitespace in your positional parameters. e.g.
input=("$#")
for ((i = 0; i < ${#input[#]}; i++)); do
printf " '%s'\n" "${input[i]}"
done
works fine, but if you use input=($*) with arguments line "a b", it will treat those as two separate arguments.
If I'm correctly understanding what you're trying to do, you can write:
input=("$#")
to copy the positional parameters into an array named input.
If you specifically want only the first five positional parameters, you can write:
input=("${#:1:5}")
Edited to add: Or are you asking, given a variable i that contains the integer 2, how you can get $2? If that's your question, then — you can use indirect expansion, where Bash retrieves the value of a variable, then uses that value as the name of the variable to substitute. Indirect expansion uses the ! character:
i=2
input[i]="${!i}" # same as input[2]="$2"
This is almost always a bad idea, though. You should rethink what you're doing.

Last empty string not included when splitting string into array by delim

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".

edit ASCII value of a character in bash

I am trying to update the ASCII value of each character of a string array in bash on which I want to add 2 to the existing character ASCII value.
Example:
declare -a x =("j" "a" "f" "a" "r")
I want to update the ASCII value incrementing the existing by 2 , such "j" will become "l"
I can't find anything dealing with the ASCII value beyond
print f '%d' "'$char"
Can anyone help me please?
And also when I try to copy an array into another it doesn't work
note that I am using
declare -a temp=("${x[#]}")
What is wrong with it?
You can turn an integer into a char by first using printf to turn it into an octal escape sequence (like \123) and then using that a printf format string to produce the character:
#!/bin/bash
char="j"
printf -v num %d "'$char"
(( num += 2 ))
printf -v newchar \\$(printf '%03o' "$num")
echo "$newchar"
This only works for ASCII.
It seems tr can help you here:
y=($(echo ${x[#]} | tr a-z c-zab))
tr maps characters from one set to another. In this example, from the set of a b c ... z, it maps to c d e ... z a b. So, you're effectively "rotating" the characters. This principle is used by the ROT13 cipher.

Resources