Bash: Split a string by delimiter ignoring spaces - linux

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

Related

Loop by taking variable values

I am trying to write bash script and how can i take values defined in variable in pipe delimiter format and print them.
Below is the algorithm i am thinking, but not sure about bash commands.
#!/bin/bash
variable1="A|B|C"
if [ ! -z "$variable1" ]
count=#should return 3 as there are 3 values (A|B|C)
fi
while count>0; do
derivedvariable=#should get A, B, C in loop
print $derivedvariable
done
output should be:
A
B
C
Use array to store elements of your input delimited by a character:
s='A|B|C'
IFS='|' read -ra arr <<< "$s"
# length:
echo "${#arr[#]}"
# looping array:
for i in "${arr[#]}"; do
echo "$i"
done
Output:
3
A
B
C
Use IFS to specify the field separator when splitting words after variable expansion.
variable1='A|B|C'
IFS='|'
for i in $variable1
do
echo "$i"
done
Use grep or tr:
grep -o "[^|]*" <<< "${variable1}"
tr '|' '\n' <<< "${variable1}"

What is the proper way to convert first character of a string from upper to lower case in shell scripting on Mac OS? [duplicate]

I want to uppercase just the first character in my string with bash.
foo="bar";
//uppercase first character
echo $foo;
should print "Bar";
One way with bash (version 4+):
foo=bar
echo "${foo^}"
prints:
Bar
foo="$(tr '[:lower:]' '[:upper:]' <<< ${foo:0:1})${foo:1}"
One way with sed:
echo "$(echo "$foo" | sed 's/.*/\u&/')"
Prints:
Bar
$ foo="bar";
$ foo=`echo ${foo:0:1} | tr '[a-z]' '[A-Z]'`${foo:1}
$ echo $foo
Bar
To capitalize first word only:
foo='one two three'
foo="${foo^}"
echo $foo
One two three
To capitalize every word in the variable:
foo="one two three"
foo=( $foo ) # without quotes
foo="${foo[#]^}"
echo $foo
One Two Three
(works in bash 4+)
Using awk only
foo="uNcapItalizedstrIng"
echo $foo | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'
Here is the "native" text tools way:
#!/bin/bash
string="abcd"
first=`echo $string|cut -c1|tr [a-z] [A-Z]`
second=`echo $string|cut -c2-`
echo $first$second
just for fun here you are :
foo="bar";
echo $foo | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1'
# or
echo ${foo^}
# or
echo $foo | head -c 1 | tr [a-z] [A-Z]; echo $foo | tail -c +2
# or
echo ${foo:1} | sed -e 's/^./\B&/'
It can be done in pure bash with bash-3.2 as well:
# First, get the first character.
fl=${foo:0:1}
# Safety check: it must be a letter :).
if [[ ${fl} == [a-z] ]]; then
# Now, obtain its octal value using printf (builtin).
ord=$(printf '%o' "'${fl}")
# Fun fact: [a-z] maps onto 0141..0172. [A-Z] is 0101..0132.
# We can use decimal '- 40' to get the expected result!
ord=$(( ord - 40 ))
# Finally, map the new value back to a character.
fl=$(printf '%b' '\'${ord})
fi
echo "${fl}${foo:1}"
This works too...
FooBar=baz
echo ${FooBar^^${FooBar:0:1}}
=> Baz
FooBar=baz
echo ${FooBar^^${FooBar:1:1}}
=> bAz
FooBar=baz
echo ${FooBar^^${FooBar:2:2}}
=> baZ
And so on.
Sources:
Bash Manual: Shell Parameter Expansion
Full Bash Guide: Parameters
Bash Hacker's Wiki Parameter Expansion
Inroductions/Tutorials:
Cyberciti.biz: 8. Convert to upper to lower case or vice versa
Opensource.com: An introduction to parameter expansion in Bash
This one worked for me:
Searching for all *php file in the current directory , and replace the first character of each filename to capital letter:
e.g: test.php => Test.php
for f in *php ; do mv "$f" "$(\sed 's/.*/\u&/' <<< "$f")" ; done
Alternative and clean solution for both Linux and OSX, it can also be used with bash variables
python -c "print(\"abc\".capitalize())"
returns Abc
This is POSIX sh-compatible as far as I know.
upper_first.sh:
#!/bin/sh
printf "$1" | cut -c1 -z | tr -d '\0' | tr [:lower:] [:upper:]
printf "$1" | cut -c2-
cut -c1 -z ends the first string with \0 instead of \n. It gets removed with tr -d '\0'. It also works to omit the -z and use tr -d '\n' instead, but this breaks if the first character of the string is a newline.
Usage:
$ upper_first.sh foo
Foo
$
In a function:
#!/bin/sh
function upper_first ()
{
printf "$1" | cut -c1 -z | tr -d '\0' | tr [:lower:] [:upper:]
printf "$1" | cut -c2-
}
old="foo"
new="$(upper_first "$old")"
echo "$new"
Posix compliant and with less sub-processes:
v="foo[Bar]"
printf "%s" "${v%"${v#?}"}" | tr '[:lower:]' '[:upper:]' && printf "%s" "${v#?}"
==> Foo[Bar]
first-letter-to-lower () {
str=""
space=" "
for i in $#
do
if [ -z $(echo $i | grep "the\|of\|with" ) ]
then
str=$str"$(echo ${i:0:1} | tr '[A-Z]' '[a-z]')${i:1}$space"
else
str=$str${i}$space
fi
done
echo $str
}
first-letter-to-upper-xc () {
v-first-letter-to-upper | xclip -selection clipboard
}
first-letter-to-upper () {
str=""
space=" "
for i in $#
do
if [ -z $(echo $i | grep "the\|of\|with" ) ]
then
str=$str"$(echo ${i:0:1} | tr '[a-z]' '[A-Z]')${i:1}$space"
else
str=$str${i}$space
fi
done
echo $str
}
first-letter-to-lower-xc(){
v-first-letter-to-lower | xclip -selection clipboard
}
Not exactly what asked but quite helpful
declare -u foo #When the variable is assigned a value, all lower-case characters are converted to upper-case.
foo=bar
echo $foo
BAR
And the opposite
declare -l foo #When the variable is assigned a value, all upper-case characters are converted to lower-case.
foo=BAR
echo $foo
bar
What if the first character is not a letter (but a tab, a space, and a escaped double quote)? We'd better test it until we find a letter! So:
S=' \"รณ foo bar\"'
N=0
until [[ ${S:$N:1} =~ [[:alpha:]] ]]; do N=$[$N+1]; done
#F=`echo ${S:$N:1} | tr [:lower:] [:upper:]`
#F=`echo ${S:$N:1} | sed -E -e 's/./\u&/'` #other option
F=`echo ${S:$N:1}
F=`echo ${F} #pure Bash solution to "upper"
echo "$F"${S:(($N+1))} #without garbage
echo '='${S:0:(($N))}"$F"${S:(($N+1))}'=' #garbage preserved
Foo bar
= \"Foo bar=

Command to count the characters present in the variable

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

Iterate over lines instead of words in a for loop of shell script

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.

split for words separated with semicolon

I have some string like
1;2;3;4;5
I want to be able to iterate over this string taking each word one by one. For the first iteration to take 1 the next to take 2 and the last 5.
I want to have something like this
for i in $(myVar)
do
echo $i
done
but I do not know how to fill the myvar
echo '1;2;3;4;5' | tr \; \\n | while read line ; do echo $line; done
There's no need to back up the IFS variable if you assign it only for a single command:
$ IFS=';' read -a words <<<"1;2;3;4;5"
$ for word in "${words[#]}"
do
echo "$word"
done
1
2
3
4
5
Other useful syntax:
$ echo "${words[0]}"
1
$ echo "${words[#]: -1}"
5
$ echo "${words[#]}"
1 2 3 4 5
Probably the easiest way to do this is change the IFS environment variable:
OLDIFS="$IFS"
IFS=';'
for num in $a; do echo $num; done
# prints:
1
2
3
4
5
IFS="$OLDIFS"
Remember to change it back afterwards or weird things will happen! :)
From the bash man page:
IFS The Internal Field Separator that is used for word splitting
after expansion and to split lines into words with the read
builtin command. The default value is ``<space><tab><new-
line>''.
This might work for you:
array=($(sed 'y/;/ /' <<<"1;2;3;4;5"))
for word in "${array[#]}"; do echo "$word"; done
for w in $(echo '1;2;3;4;5' | tr \; \\n); do echo $w; done

Resources