I have string like
name1::1.1.1.1::ps -ax
I want to split the string based on delimiter :: using bash scripting.
The desired output should be an array of 3 elements
("name1" "1.1.1.1" "ps -ax")
without double quotes
I appreciate your help.
Assuming there are no :s in the array data, use bash pattern substitution to squeeze the :: to : while assigning the string to $array, then show the whole array, then just element #2:
a="name1::1.1.1.1::ps -ax"
IFS=: array=(${a//::/:}) ; echo ${array[#]} ; echo "${array[2]}"
Output:
name1 1.1.1.1 ps -ax
ps -ax
But what if there are :s in the array data? Specifically in the third field, (the command), and only in that field. Use read with dummy variables to absorb the extra :: separators:
a="name1::1.1.1.1::parallel echo ::: 1 2 3 ::: a b"
IFS=: read x a y b z <<< "$a"; array=("$x" "$y" "$z"); printf "%s\n" "${array[#]}"
Output:
name1
1.1.1.1
parallel echo ::: 1 2 3 ::: a b
The only safe possibility is use a loop:
a='name1::1.1.1.1::ps -ax'
array=()
a+=:: # artificially append the separator
while [[ $a ]]; do
array+=( "${a%%::*}" )
a=${a#*::}
done
This will work with any symbol in a (spaces, glob characters, newlines, etc.)
echo "name1::1.1.1.1::ps -ax" | awk -F"::" '{print $1 $2 $3}'
i=0
string="name1::1.1.1.1::ps -ax"
echo "$string" | awk 'BEGIN{FS="::";OFS="\n"}{$1=$1;print $0}'>tempFile
while read line;
do
arr["$i"]="$line"
i=$(expr $i + 1)
done<tempFile
echo "${arr[#]}"
echo "${arr[0]}"
echo "${arr[1]}"
echo "${arr[2]}"
Output:
sh-4.4$ ./script1.sh
name1 1.1.1.1 ps -ax
name1
1.1.1.1
ps -ax
I'm trying to write a script where the user enters a number as a parameter and the script calculates the sum of all the digits e.g.,
./myScript 963
18
So the script takes the string "963" and adds all the characters in the string 9+6+3=18. I'm thinking I could get the length of the string and use a loop to add all the indexes of the string together but I cannot figure out how to get an index of the string without already knowing the character you're looking for.
I was able to break the string up using the following command,
echo "963" | fold -w1
9
6
3
But I'm not sure if/how I could pipe | or redirect > the results into a variable and add it to a total each time.
How can I get a character of a string at a particular index?
Update:
Example 1:
$1=59 then the operation is
5+9=14
Example 2:
$1=2222 then the operation is
2+2+2+2=8
All the characters in the string are added to a total sum.
The following script loops through all of the digits in the input string and adds them together:
#!/bin/bash
s="$1"
for ((i=0; i<${#s}; ++i)); do
((t+=${s:i:1}))
done
echo "sum of digits: $t"
The syntax ${s:i:1} extracts a substring of length 1 from position i in the string $s.
Output:
$ ./add.sh 963
sum of digits: 18
If you wanted to continue adding together the digits until there was only one remaining, you could do this instead:
#!/bin/bash
s="$1"
while (( ${#s} > 1 )); do
t=0
for ((i=0; i<${#s}; ++i)); do
((t+=${s:i:1}))
done
echo "iteration $((++n)): $t"
s=$t
done
echo "final result: $s"
The outer while loop continues as long as the length of the string is greater than 1. The inner for loop adds together each digit in the string.
Output:
$ ./add.sh 963
iteration 1: 18
iteration 2: 9
final result: 9
Not that you asked for it but there are many ways to sum all of the digits in a string. Here's another one using Perl:
$ perl -MList::Util=sum -F -anE 'say sum #F' <<<639
18
List::Util is a core module in Perl. The sum subroutine does a reduction sum on a list to produce a single value. -a enables auto-split mode so the input is split into the array #F. -F is used to set the field delimiter (in this case it is blank, so every character counts as a separate field). -n processes every line of input one at a time and -E is used to enter a Perl one-liner but with newer features (such as say) enabled. say is like print but a newline is added to the output.
If you're not familiar with the <<< syntax, it is equivalent to echo 639 | perl ....
Not using string subscription but computing the desired sum:
number=963
sum=0
for d in `echo "$number" | sed 's,\(.\), \1,g'`
do
sum=$(($sum + $d))
done
echo $sum
Output: 18
I would do this:
num="963"
echo "$num" | grep -o . | paste -sd+ - | bc
#or using your fold
echo "$num" | fold -w1 | paste -sd+ - | bc
both prints
18
Explanation
the grep -o . return each digit from your number as well as the fold -w1
the paste -sd+ - merges the lines to one line using the delimiter + - e.g. create an calculation string like 9+6+3
the bc does the calculation
if you want script, e.g. digadd.sh use
grep -o . <<<"$1" | paste -sd+ - | bc
using it
$ bash digadd.sh #nothing
$ #will return nothing
$ bash digadd.sh 1273617617273450359345873647586378242349239471289638982
268
$
For fun, doing this in loop until the result is only 1 digit
num=12938932923849028940802934092840924
while [[ ${#num} > 1 ]]
do
echo -n "sum of digits for $num is:"
num=$(echo "$num" | grep -o . | paste -sd+ - | bc)
echo $num
done
echo "final result: $num"
prints
sum of digits for 12938932923849028940802934092840924 is:159
sum of digits for 159 is:15
sum of digits for 15 is:6
final result: 6
another fun variant, what will extract all digits from any string is:
grep -oP '\d' <<<"$1" | paste -sd+ - | bc
so using it in the script digadd.sh like
bash digadd.sh 9q6w3
produces
18
The answer for your question in the title: To getting the Nth character from any string you can use
echo "$string:POSITION:length" #position from 0
e.g. to get the 1st digit
echo "${num:0:1}"
You can use cut with -c parameter to get character at any position. for example:
echo "963" | cut -c1
Outputs: 9
Using awk:
awk 'split($0,a,""){for(i in a) sum+=i}END{print sum}' <<<$1
This can be done using substring manipulation (supported by busybox ash, but not posix sh compliant)
#!/bin/ash
i=0
sum=0
while [ $i -lt ${#1} ]; do
sum=$((sum+${1:i:1}));
i=$((i+1))
done
echo $sum
If you really must have a posix shell compliant version, you can use:
#!/bin/sh
sum=0
A=$1
while [ ${#B} -lt ${#A} ];do
B=$B?
done
while [ "$A" ]; do
B=${B#?*}
sum=$((sum+${A%$B}))
A=${A#?*}
done
echo $sum
I am trying to count the number of characters present in the variable. I used the below shell command. But I am getting error - command not found in line 4
#!/bin/bash
for i in one; do
n = $i | wc -c
echo $n
done
Can someone help me in this?
In bash you can just write ${#string}, which will return the length of the variable string, i.e. the number of characters in it.
Something like this:
#!/bin/bash
for i in one; do
n=$(echo $i | wc -c)
echo $n
done
Assignments in bash cannot have a space before the equals sign. In addition, you want to capture the output of the command you run and assign that to $n, rather than that statement which would probably just assign $i to $n.
Use the following instead:
#!/bin/bash
for i in one; do
n=`$i | wc -c`
echo $n
done
It can be as simple as that:
str="abcdef"; wc -c <<< "$str"
7
But mind you that end of line counts as a character:
str="abcdef"; cat -A <<< "$str"
abcdef$
If you need to remove it:
str="abcdef"; tr -d '\n' <<< "$str" | wc -c
6
Following is the shell script to read all the DSF present in the box. But since the line is having spaces, it is displaying them in different lines.
For those of you who dont understand ioscan -m dsf, replace it by ls -ltr, then the output is such that the permission and names are displayed in different line, but i want them in the same line.
#!/usr/bin/ksh
for a in `ioscan -m dsf`
do
echo $a
done
The for loop is not designed to loop over "lines". Instead it loops over "words".
Short simplified terminology: "lines" are things separated by newlines. "words" are things separated by spaces. in bash lingo "words" are called "fields".
The idiomatic way to loop over lines is to use a while loop in combination with read.
ioscan -m dsf | while read -r line
do
printf '%s\n' "$line"
done
Note that the while loop is in a subshell because of the pipe. This can cause some confusion with variable scope. In bash you can work around this by using process substitution.
while read -r line
do
printf '%s\n' "$line"
done < <(ioscan -m dsf)
But now the "generator" (ioscan in this example) is in a subshell.
For more information about the subshell problematic in loops see http://mywiki.wooledge.org/BashFAQ/024
If you insist on using a for loop to loop over lines you have to change the value of $IFS to only newline. IFS is short for Internal Field Separator. Usually $IFS contains a space, a tab, and a newline.
Here is the typical way to do so:
OLDIFS="$IFS"
IFS=$'\n' # bash specific
for line in $(ioscan -m dsf)
do
printf '%s\n' "$line"
done
IFS="$OLDIFS"
(the bash specific part ($'\n') is called ANSI-C Quoting)
But beware many commands depends on some sane setting for $IFS. I do not recommend changing $IFS. Too often it will cause an endless nightmare of obscure bug hunting.
See also:
http://wiki.bash-hackers.org/syntax/ccmd/classic_for
http://wiki.bash-hackers.org/commands/builtin/read
http://mywiki.wooledge.org/IFS
http://mywiki.wooledge.org/SubShell
http://mywiki.wooledge.org/ProcessSubstitution
Using for
for l in $() performs word splitting based on IFS:
$ for l in $(printf %b 'a b\nc'); do echo "$l"; done
a
b
c
$ IFS=$'\n'; for l in $(printf %b 'a b\nc'); do echo "$l"; done
a b
c
IFS doesn't have to be set back if it is not used later.
for l in $() also performs pathname expansion:
$ printf %b 'a\n*\n' > file.txt
$ IFS=$'\n'
$ for l in $(<file.txt); do echo "$l"; done
a
file.txt
$ set -f; for l in $(<file.txt); do echo "$l"; done; set +f
a
*
If IFS=$'\n', linefeeds are stripped and collapsed:
$ printf %b '\n\na\n\nb\n\n' > file.txt
$ IFS=$'\n'; for l in $(<file.txt); do echo "$l"; done
a
b
$(cat file.txt) (or $(<file.txt)) also reads the whole file to memory.
Using read
Without -r backslashes are used for line continuation and removed before other characters:
$ cat file.txt
\1\\2\
3
$ cat file.txt | while read l; do echo "$l"; done
1\23
$ cat file.txt | while read -r l; do echo "$l"; done
\1\\2\
3
Characters in IFS are stripped from the start and end of lines but not collapsed:
$ printf %b '1 2 \n\t3\n' | while read -r l; do echo "$l"; done
1 2
3
$ printf %b ' 1 2 \n\t3\n' | while IFS= read -r l; do echo "$l"; done
1 2
3
If the last line doesn't end with a newline, read assigns l to it but exits before the body of the loop:
$ printf 'x\ny' | while read l; do echo $l; done
x
$ printf 'x\ny' | while read l || [[ $l ]]; do echo $l; done
x
y
If a while loop is in a pipeline, it is also in a subshell, so variables are not visible outside it:
$ x=0; seq 3 | while read l; do let x+=l; done; echo $x
0
$ x=0; while read l; do let x+=l; done < <(seq 3); echo $x
6
$ x=0; x=8 | x=9; echo $x
0
you need to use this basically IFS=$'\n' and grep -x instead of grep as it will work like a equal to operator instead of like operator.
I have a string in a Bash shell script that I want to split into an array of characters, not based on a delimiter but just one character per array index. How can I do this? Ideally it would not use any external programs. Let me rephrase that. My goal is portability, so things like sed that are likely to be on any POSIX compatible system are fine.
Try
echo "abcdefg" | fold -w1
Edit: Added a more elegant solution suggested in comments.
echo "abcdefg" | grep -o .
You can access each letter individually already without an array conversion:
$ foo="bar"
$ echo ${foo:0:1}
b
$ echo ${foo:1:1}
a
$ echo ${foo:2:1}
r
If that's not enough, you could use something like this:
$ bar=($(echo $foo|sed 's/\(.\)/\1 /g'))
$ echo ${bar[1]}
a
If you can't even use sed or something like that, you can use the first technique above combined with a while loop using the original string's length (${#foo}) to build the array.
Warning: the code below does not work if the string contains whitespace. I think Vaughn Cato's answer has a better chance at surviving with special chars.
thing=($(i=0; while [ $i -lt ${#foo} ] ; do echo ${foo:$i:1} ; i=$((i+1)) ; done))
As an alternative to iterating over 0 .. ${#string}-1 with a for/while loop, there are two other ways I can think of to do this with only bash: using =~ and using printf. (There's a third possibility using eval and a {..} sequence expression, but this lacks clarity.)
With the correct environment and NLS enabled in bash these will work with non-ASCII as hoped, removing potential sources of failure with older system tools such as sed, if that's a concern. These will work from bash-3.0 (released 2005).
Using =~ and regular expressions, converting a string to an array in a single expression:
string="wonkabars"
[[ "$string" =~ ${string//?/(.)} ]] # splits into array
printf "%s\n" "${BASH_REMATCH[#]:1}" # loop free: reuse fmtstr
declare -a arr=( "${BASH_REMATCH[#]:1}" ) # copy array for later
The way this works is to perform an expansion of string which substitutes each single character for (.), then match this generated regular expression with grouping to capture each individual character into BASH_REMATCH[]. Index 0 is set to the entire string, since that special array is read-only you cannot remove it, note the :1 when the array is expanded to skip over index 0, if needed.
Some quick testing for non-trivial strings (>64 chars) shows this method is substantially faster than one using bash string and array operations.
The above will work with strings containing newlines, =~ supports POSIX ERE where . matches anything except NUL by default, i.e. the regex is compiled without REG_NEWLINE. (The behaviour of POSIX text processing utilities is allowed to be different by default in this respect, and usually is.)
Second option, using printf:
string="wonkabars"
ii=0
while printf "%s%n" "${string:ii++:1}" xx; do
((xx)) && printf "\n" || break
done
This loop increments index ii to print one character at a time, and breaks out when there are no characters left. This would be even simpler if the bash printf returned the number of character printed (as in C) rather than an error status, instead the number of characters printed is captured in xx using %n. (This works at least back as far as bash-2.05b.)
With bash-3.1 and printf -v var you have slightly more flexibility, and can avoid falling off the end of the string should you be doing something other than printing the characters, e.g. to create an array:
declare -a arr
ii=0
while printf -v cc "%s%n" "${string:(ii++):1}" xx; do
((xx)) && arr+=("$cc") || break
done
If your string is stored in variable x, this produces an array y with the individual characters:
i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1}; i=$((i+1));done
The most simple, complete and elegant solution:
$ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')
and test
$ echo ${ARRAY[0]}
a
$ echo ${ARRAY[1]}
b
Explanation: read -a reads the stdin as an array and assigns it to the variable ARRAY treating spaces as delimiter for each array item.
The evaluation of echoing the string to sed just add needed spaces between each character.
We are using Here String (<<<) to feed the stdin of the read command.
I have found that the following works the best:
array=( `echo string | grep -o . ` )
(note the backticks)
then if you do: echo ${array[#]} ,
you get: s t r i n g
or: echo ${array[2]} ,
you get: r
Pure Bash solution with no loop:
#!/usr/bin/env bash
str='The quick brown fox jumps over a lazy dog.'
# Need extglob for the replacement pattern
shopt -s extglob
# Split string characters into array (skip first record)
# Character 037 is the octal representation of ASCII Record Separator
# so it can capture all other characters in the string, including spaces.
IFS= mapfile -s1 -t -d $'\37' array <<<"${str//?()/$'\37'}"
# Strip out captured trailing newline of here-string in last record
array[-1]="${array[-1]%?}"
# Debug print array
declare -p array
string=hello123
for i in $(seq 0 ${#string})
do array[$i]=${string:$i:1}
done
echo "zero element of array is [${array[0]}]"
echo "entire array is [${array[#]}]"
The zero element of array is [h]. The entire array is [h e l l o 1 2 3 ].
Yet another on :), the stated question simply says 'Split string into character array' and don't say much about the state of the receiving array, and don't say much about special chars like and control chars.
My assumption is that if I want to split a string into an array of chars I want the receiving array containing just that string and no left over from previous runs, yet preserve any special chars.
For instance the proposed solution family like
for (( i=0 ; i < ${#x} ; i++ )); do y[i]=${x:i:1}; done
Have left overs in the target array.
$ y=(1 2 3 4 5 6 7 8)
$ x=abc
$ for (( i=0 ; i < ${#x} ; i++ )); do y[i]=${x:i:1}; done
$ printf '%s ' "${y[#]}"
a b c 4 5 6 7 8
Beside writing the long line each time we want to split a problem, so why not hide all this into a function we can keep is a package source file, with a API like
s2a "Long string" ArrayName
I got this one that seems to do the job.
$ s2a()
> { [ "$2" ] && typeset -n __=$2 && unset $2;
> [ "$1" ] && __+=("${1:0:1}") && s2a "${1:1}"
> }
$ a=(1 2 3 4 5 6 7 8 9 0) ; printf '%s ' "${a[#]}"
1 2 3 4 5 6 7 8 9 0
$ s2a "Split It" a ; printf '%s ' "${a[#]}"
S p l i t I t
If the text can contain spaces:
eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
$ echo hello | awk NF=NF FS=
h e l l o
Or
$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
I know this is a "bash" question, but please let me show you the perfect solution in zsh, a shell very popular these days:
string='this is a string'
string_array=(${(s::)string}) #Parameter expansion. And that's it!
print ${(t)string_array} -> type array
print $#string_array -> 16 items
This is an old post/thread but with a new feature of bash v5.2+ using the shell option patsub_replacement and the =~ operator for regex. More or less same with #mr.spuratic post/answer.
str='There can be only one, the Highlander.'
regexp="${str//?/(&)}"
[[ "$str" =~ $regexp ]] &&
printf '%s\n' "${BASH_REMATCH[#]:1}"
Or by just: (which includes the whole string at index 0)
declare -p BASH_REMATCH
If that is not desired, one can remove the value of the first index (index 0), with
unset -v 'BASH_REMATCH[0]'
instead of using printf or echo to print the value of the array BASH_REMATCH
One can check/see the value of the variable "$regexp" with either
declare -p regexp
Output
declare -- regexp="(T)(h)(e)(r)(e)( )(c)(a)(n)( )(b)(e)( )(o)(n)(l)(y)( )(o)(n)(e)(,)( )(t)(h)(e)( )(H)(i)(g)(h)(l)(a)(n)(d)(e)(r)(.)"
or
echo "$regexp"
Using it in a script, one might want to test if the shopt is enabled or not, although the manual says it is on/enabled by default.
Something like.
if ! shopt -q patsub_replacement; then
shopt -s patsub_replacement
fi
But yeah, check the bash version too! If you're not sure which version of bash is in use.
if ! ((BASH_VERSINFO[0] >= 5 && BASH_VERSINFO[1] >= 2)); then
printf 'No dice! bash version 5.2+ is required!\n' >&2
exit 1
fi
Space can be excluded from regexp variable, change it from
regexp="${str//?/(&)}"
To
regexp="${str//[! ]/(&)}"
and the output is:
declare -- regexp="(T)(h)(e)(r)(e) (c)(a)(n) (b)(e) (o)(n)(l)(y) (o)(n)(e) (t)(h)(e) (H)(i)(g)(h)(l)(a)(n)(d)(e)(r)(.)"
Maybe not as efficient as the other post/answer but it is still a solution/option.
If you want to store this in an array, you can do this:
string=foo
unset chars
declare -a chars
while read -N 1
do
chars[${#chars[#]}]="$REPLY"
done <<<"$string"x
unset chars[$((${#chars[#]} - 1))]
unset chars[$((${#chars[#]} - 1))]
echo "Array: ${chars[#]}"
Array: f o o
echo "Array length: ${#chars[#]}"
Array length: 3
The final x is necessary to handle the fact that a newline is appended after $string if it doesn't contain one.
If you want to use NUL-separated characters, you can try this:
echo -n "$string" | while read -N 1
do
printf %s "$REPLY"
printf '\0'
done
AWK is quite convenient:
a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'
where FS and OFS is delimiter for read-in and print-out
For those who landed here searching how to do this in fish:
We can use the builtin string command (since v2.3.0) for string manipulation.
↪ string split '' abc
a
b
c
The output is a list, so array operations will work.
↪ for c in (string split '' abc)
echo char is $c
end
char is a
char is b
char is c
Here's a more complex example iterating over the string with an index.
↪ set --local chars (string split '' abc)
for i in (seq (count $chars))
echo $i: $chars[$i]
end
1: a
2: b
3: c
zsh solution: To put the scalar string variable into arr, which will be an array:
arr=(${(ps::)string})
If you also need support for strings with newlines, you can do:
str2arr(){ local string="$1"; mapfile -d $'\0' Chars < <(for i in $(seq 0 $((${#string}-1))); do printf '%s\u0000' "${string:$i:1}"; done); printf '%s' "(${Chars[*]#Q})" ;}
string=$(printf '%b' "apa\nbepa")
declare -a MyString=$(str2arr "$string")
declare -p MyString
# prints declare -a MyString=([0]="a" [1]="p" [2]="a" [3]=$'\n' [4]="b" [5]="e" [6]="p" [7]="a")
As a response to Alexandro de Oliveira, I think the following is more elegant or at least more intuitive:
while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
declare -r some_string='abcdefghijklmnopqrstuvwxyz'
declare -a some_array
declare -i idx
for ((idx = 0; idx < ${#some_string}; ++idx)); do
some_array+=("${some_string:idx:1}")
done
for idx in "${!some_array[#]}"; do
echo "$((idx)): ${some_array[idx]}"
done
Pure bash, no loop.
Another solution, similar to/adapted from Léa Gris' solution, but using read -a instead of readarray/mapfile :
#!/usr/bin/env bash
str='azerty'
# Need extglob for the replacement pattern
shopt -s extglob
# Split string characters into array
# ${str//?()/$'\x1F'} replace each character "c" with "^_c".
# ^_ (Control-_, 0x1f) is Unit Separator (US), you can choose another
# character.
IFS=$'\x1F' read -ra array <<< "${str//?()/$'\x1F'}"
# now, array[0] contains an empty string and the rest of array (starting
# from index 1) contains the original string characters :
declare -p array
# Or, if you prefer to keep the array "clean", you can delete
# the first element and pack the array :
unset array[0]
array=("${array[#]}")
declare -p array
However, I prefer the shorter (and easier to understand for me), where we remove the initial 0x1f before assigning the array :
#!/usr/bin/env bash
str='azerty'
shopt -s extglob
tmp="${str//?()/$'\x1F'}" # same as code above
tmp=${tmp#$'\x1F'} # remove initial 0x1f
IFS=$'\x1F' read -ra array <<< "$tmp" # assign array
declare -p array # verification