Unset IFS value for shell script - shell

In my use case I would like to change the value of IFS to a known separator (-). I tried the following:
OLDIFS=$IFS
IFS='-'
for x in $*
do
echo $x
done
IFS=$OLDIFS
When using e.g. -a b -c d as input string I expect the output to be
a b
c d
However, what I get is
a
b
c
d
I'm on AIX.

I tried your code and I get
a b
c d
Try this
$ cat >a <<.
#!/bin/sh
OLDIFS=$IFS
IFS='-'
for x in $*
do
echo $x
done
IFS=$OLDIFS
.
$ chmod +x a
$ ./a "-a b -c d"
a b
c d
$

Here is one way of getting this output using awk and avoid all the IFS manipulation:
s='-a b -c d'
echo "$s" | awk -F ' *- *' '{print $2 RS $3}'
a b
c d

Related

How to generate all ASCII characters with a brace expansion?

This lists all English characters:
$ echo {A..Z}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
But how to list all ASCII characters?
I tried this:
$ echo {\!..\~}
{!..~}
and this:
$ echo {$'!'..$'~'}
{!..~}
But both did not work. Is it possible?
This uses only one printf but a more complicated brace expansion.
printf '%b' \\x{0..7}{{0..9},{a..f}}
It also works, but not as nicely (it outputs a lot of whitespace):
echo -e \\x{0..7}{{0..9},{a..f}}
$ printf '%b\n' "$(printf '\%03o' {0..127})"
123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
To see a representation of the non-printable characters in the output from the above and the characters hidden by the effect of trying to print them as-is, you can pipe it to cat -v:
$ printf '%b\n' "$(printf '\%03o' {0..127})" | cat -v
^#^A^B^C^D^E^F^G^H
^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_ !"#$%&'()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?
To print just from the ASCII code for ! (33) to the ASCII code for ~ (126):
$ printf '%b\n' "$(printf '\%03o' {33..126})"
!"#$%&'()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
and to print from ! to ~ without having to know their numeric values:
$ printf '%b\n' "$(eval printf '\\%03o' $(printf '{%d..%d}' "'!" "'~"))"
!"#$%&'()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
which you can use with shell variables to hold the beginning and ending chars:
$ beg='!'; end='~';
$ printf '%b\n' "$(eval printf '\\%03o' $(printf '{%d..%d}' "'$beg" "'$end"))"
!"#$%&'()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

Name (and set) variables in current shell, based on line input data

I have a SQL*Plus output written into a text file in the following format:
3459906| |2|X1|WAS1| Output1
334596| |2|X1|WAS2| Output1
3495792| |1|X1|WAS1| Output1
687954| |1|X1|WAS2| Output1
I need a shell script to fetch the counts which were at the beginning based on the text after the counts.
For example, If the Text is like |2|X1|WAS1| , then 3459906 should be passed on to a variable x1was12 and if the text is like |2|X1|WAS2| , then 334596 should be passed on to a variable x1was22.
I tried writing a for loop and if condition to pass on the counts, but was unsuccessful:
export filename1='file1.dat'
while read -r line ; do
if [[ grep -i "*|2|X1|WAS1| Output1*" | wc -l -eq 0 ]] ; then
export xwas12=sed -n ${line}p $filename1 | \
sed 's/[^0-9]*//g' | sed 's/..$//'
elif [[ grep -i "*|2|X1|WAS2| Output1*" | wc -l -eq 0 ]] ; then
export x1was22=sed -n ${line}p $filename1 | \
sed 's/[^0-9]*//g' | sed 's/..$//'
elif [[ grep -i "*|1|X1|WAS1| Output1*" | wc -l -eq 0 ]] ; then
export x1was11=sed -n ${line}p $filename1 | \
sed 's/[^0-9]*//g' | sed 's/..$//'
elif [[ grep -i "*|1|X1|WAS2| Output1*" | wc -l -eq 0 ]]
export x1was21=sed -n ${line}p $filename1 | \
sed 's/[^0-9]*//g' | sed 's/..$//'
fi
done < "$filename1"
echo '$x1was12' > output.txt
echo '$x1was22' >> output.txt
echo '$x1was11' >> output.txt
echo '$x1was21' >> output.txt
What I was trying to do was:
Go to the first line in the file
-> Search for the text and if found then assign the sed output to the variable
Then go to the second line of the file
-> Search for the texts in the if commands and assign the sed output to another variable.
same goes for other
while IFS='|' read -r count _ n x was _; do
# remove spaces from all variables
count=${count// /}; n=${n// /}; x=${x// /}; was=${was// /}
varname="${x}${was}${n}"
printf -v "${varname,,}" %s "$count"
done <<'EOF'
3459906| |2|X1|WAS1| Output1
334596| |2|X1|WAS2| Output1
3495792| |1|X1|WAS1| Output1
687954| |1|X1|WAS2| Output1
EOF
With the above executed:
$ echo "$x1was12"
3459906
Of course, the redirection from a heredoc could be replaced with a redirection from a file as well.
How does this work? Let's break it down:
Every time IFS='|' read -r count _ n x was _ is run, it reads a single line, separating it by |s, putting the first column into count, discarding the second by assigning it to _, reading the third into n, the fourth into x, the fifth into was, and the sixth and all following content into _. This practice is discussed in detail in BashFAQ #1.
count=${count// /} is a parameter expansion which prunes spaces from the variable count, by replacing all such spaces with empty strings. See also BashFAQ #100.
"${varname,,}" is another parameter expansion, this one converting a variable's contents to all-lowercase. (This requires bash 4.0; in prior versions, consider "$(tr '[:upper:]' '[:lower:]' <<<"$varname") as a less-efficient alternative).
printf -v "$varname" %s "value" is a mechanism for doing an indirect assignment to the variable named in the variable varname.
If not for the variable names, the whole thing could be done with two commands:
cut -d '|' -f1 file1.dat | tr -d ' ' > output.txt
The variable names make it more interesting. Two bash methods follow, plus a POSIX method...
The following bash code ought to do what the OP's sample code was
meant to do:
declare $(while IFS='|' read a b c d e f ; do
echo $a 1>&2 ; echo x1${e,,}$c=${a/ /}
done < file1.dat 2> output.txt )
Notes:
The bash shell is needed for ${e,,}, (turns "WAS" into "was"), and $a/ /} , (removes a leading space that might be in
$a), and declare.
The while loop parses file1.dat and outputs a bunch of variable assignments. Without the declare this code:
while IFS='|' read a b c d e f ; do
echo x1${e,,}$c=${a/ /} ;
done < file1.dat
Outputs:
x1was12=3459906
x1was22=334596
x1was11=3495792
x1was21=687954
The while loop outputs to two separate streams: stdout (for the declare), and stderr (using the 1>&2 and 2> redirects for
output.txt).
Using bash associative arrays:
declare -A x1was="( $(while IFS='|' read a b c d e f ; do
echo $a 1>&2 ; echo [${e/WAS/}$c]=${a/ /}
done < file1.dat 2> output.txt ) )"
In which case the variable names require brackets:
echo ${x1was[21]}
687954
POSIX shell code (tested using dash):
eval $(while IFS='|' read a b c d e f ; do
echo $a 1>&2; echo x1$(echo $e | tr '[A-Z]' '[a-z]')$c=$(echo $a)
done < file1.dat 2> output.txt )
eval should not be used if there's any doubt about what's in file1.dat. The above code assumes the data in file1.dat is
uniformly dependable.

bash command output as parameters

Assume that the command alpha produces this output:
a b c
d
If I run the command
beta $(alpha)
then beta will be executed with four parameters, "a", "b", "c" and "d".
But if I run the command
beta "$(alpha)"
then beta will be executed with one parameter, "a b c d".
What should I write in order to execute beta with two parameters, "a b c" and "d". That is, how do I force $(alpha) to return one parameter per output line from alpha?
You can use:
$ alpha | xargs -d "\n" beta
Similar to anubhava's answer, if you are using bash 4 or later.
readarray -t args < <(alpha)
beta "${args[#]}"
Do that in 2 steps in bash:
IFS=$'\n' read -d '' a b < <(alpha)
beta "$a" "$b"
Example:
# set IFS to \n with -d'' to read 2 values in a and b
IFS=$'\n' read -d '' a b < <(echo $'a b c\nd')
# check a and b
declare -p a b
declare -- a="a b c"
declare -- b="d"
Script beta.sh should fix your issue:
$ cat alpha.sh
#! /bin/sh
echo -e "a b c\nd"
$ cat beta.sh
#! /bin/sh
OIFS="$IFS"
IFS=$'\n'
for i in $(./alpha.sh); do
echo $i
done

why read command reads all words into first name

My script:
#!/bin/bash
IFS=','
read a b c d e f g <<< $(echo "1,2,3,4,5,6,7") # <- this could be any other commands, I am just making up a dummy command call
echo $a
echo $b
echo $c
I expected it to output
1
2
3
But instead it outputs:
1 2 3 4 5 6 7
blank line
blank line
What did I do wrong?
You should use it like this:
IFS=, read a b c d e f g <<< "1,2,3,4,5,6,7"
Use IFS in same line as read to avoid cluttering the current shell environment.
And avoid using command substitution just to capture the output of a single echo command.
If you want to use a command's output in read then better use process substitution in bash:
IFS=, read a b c d e f g < <(echo "1,2,3,4,5,6,7")
This works:
#!/bin/bash
IFS=','
read a b c d e f g <<< "$(echo "1,2,3,4,5,6,7")"
echo $a; echo $b; echo $c
Note the quoting: "$( ...)". Without it, the string is split and becomes
$(echo "1,2,3,4,5,6,7") ===> 1 2 3 4 5 6 7
Giving 1 2 3 4 5 6 7 to read produces no splitting, as the IFS is ,.
Of course, this also works (IFS only apply to the executed command: read):
#!/bin/bash
IFS=',' read a b c d e f g <<< "$(echo "1,2,3,4,5,6,7")"
echo $a; echo $b; echo $c
And is even better like this:
#!/bin/bash
IFS=',' read a b c d e f g <<< "1,2,3,4,5,6,7"
echo $a; echo $b; echo $c
You do not need to "execute an echo" to get a variable, you already have it.
Technically, your code is correct. There is a bug in here-string handling in bash 4.3 and earlier that incorrectly applies word-splitting to the unquoted expansion of the command substitution. The following would work around the bug:
# Quote the expansion to prevent bash from splitting the expansion
# to 1 2 3 4 5 6 7
$ read a b c d e f g <<< "$(echo "1,2,3,4,5,6,7")"
as would
# A regular string is not split
$ read a b c d e f g <<< 1,2,3,4,5,6,7
In bash 4.4, this seems to be fixed:
$ echo $BASH_VERSION
4.4.0(1)-beta
$ IFS=,
$ read a b c d e f g <<< $(echo "1,2,3,4,5,6,7")
$ echo $a
1

Easy Bash Cut Delimiter

I have this..
$input = "echo a b c d"
echo -e "$input" | cut -d " " -f 2-
but I just want a simple cut that will get rid of echo as well as print
a b c d #(single space) only
echo -e "$input" | tr -s ' ' | cut -d " " -f2-
Also gets rid of the 'echo'.
You don't need any tools besides what bash provides built-in.
[ghoti#pc ~]$ input="echo a b c d"
[ghoti#pc ~]$ output=${input// / }
[ghoti#pc ~]$ echo $output
echo a b c d
[ghoti#pc ~]$ echo ${output#* }
a b c d
[ghoti#pc ~]$
Up-side: you avoid the extra overhead of pipes.
Down-side: you need to assign an extra variable, because you can't do complex pattern expansion within complex pattern expansion (i.e. echo ${${input//  / }#* } won't work).
A little roundabout, but interesting:
( set -- $input; shift; echo $# )
With sed:
sed -e 's/[ ]*[^ ]*[ ]*\(.*\)/\1/' -e 's/[ ]*/ /g' -e 's/^ *//' input_file

Resources