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
Related
I am trying to write two loops that would make a function together as I require both variables in one deployment for azure such as a name of storage account and container name to contain their key and store it but I am getting repeated results.
for storage in $(cat $TMP_FILE_STORAGE | sed 's/^[^"]*"\([^"]*\)".*/\1/' )
do
echo $storage
for container in $(cat $TMP_FILE_CONTAINER| sed 's/^[^"]*"\([^"]*\)".*/\1/' )
do
echo $container
continue
done
done
This is the file for container json :
lama baba
This is the file for storage json :
abdelvt33cpgsa abdelvt44cpgsa
This is the output I am getting
abdelvt33cpgsa
lama
baba
abdelvt44cpgsa
lama
baba
and the expected result should be
abdelvt33cpgsa
lama
abdelvt44cpgsa
baba
See Bash FAQ 001; you shouldn't be using for loops in the first place.
Instead, use a while loop with two separate file descriptors.
while IFS= read -r storage <&3 && IFS= read -r container <&4 ; do
echo "$storage"
echo "$container"
done 3< <(sed 's/^[^"]*"\([^"]*\)".*/\1/' < "$TMP_FILE_STORAGE" ) 4< <(sed 's/^[^"]*"\([^"]*\)".*/\1/' < "$TMP_FILE_CONTAINER")
It appears you might be able to get rid of sed as well by splitting each input line on ":
while IFS=\" read -r _ storage _ <&3 &&
IFS=\" read -r _ container _ <&4; do
echo "$storage"
echo "$container"
done 3< "$TMP_FILE_STORAGE" 4< "$TMP_FILE_CONTAINER"
Assuming
your files don't contain tab characters, and
they have the same number of lines,
a quick way is to paste both files and then convert the separator character (tab by default) to newlines:
$ cat A-file
A
B
C
D D
E E
$ cat 1-file
1
2
3
4 4
5 5
$ paste A-file 1-file|tr '\t' '\n'
A
1
B
2
C
3
D D
4 4
E E
5 5
Look how Stackoverflow syntax coloring makes this example look cool!
The right solution to this was to run it over aray loop.
Potato=$(cat $TMP_FILE_STORAGE)
Potato1=$(cat $TMP_FILE_CONTAINER)
eval "array=($Potato)"
eval "array2=($Potato1)"
for ((i=0; i<${#array[#]}; i++)); do
end=`date -d "15 minutes" '+%Y-%m-%dT%H:%MZ'`
az storage container generate-sas -n ${array2[$i]} --account-name ${array[$i]} --https-only --permissions dlrw --expiry $end -otsv`
echo "First parameter : ${array[$i]} -- second parameter : ${array2[$i]}"
done
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
I have a file like:
1
2
3
4
5
a
b
c
d
e
And want to put it like:
1 a
2 b
3 c
4 d
5 e
Is there a quick way to do it in bash?
pr is the tool to use for columnizing data:
pr -s" " -T -2 filename
With paste and process substitution:
$ paste -d " " <(sed -n '1,/^$/{/^$/d;p}' file) <(sed -n '/^$/,${//!p}' file)
1 a
2 b
3 c
4 d
5 e
Simple bash script the does the job:
nums=()
is_line=0
cat ${1} | while read line
do
if [[ ${line} == '' ]]
then
is_line=1
else
if [[ ${is_line} == 0 ]]
then
nums=("${nums[#]}" "${line}")
else
echo ${nums[0]} ${line}
nums=(${nums[*]:1})
fi
fi
done
Run it like this: ./script filename
Example:
$ ./script filein
1 a
2 b
3 c
4 d
5 e
$ rs 2 5 <file | rs -T
1 a
2 b
3 c
4 d
5 e
If you want that extra separator space off, use -g1 in the latter rs. Explained:
print file in 5 cols and 2 rows
-T transpose it
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
In a bash script I get to this point
read ENE CX CY CZ <<< $(head -n 1 RESULTS_${lach}tal2)
echo $ENE
SED_ARG="-e 's/-/m/g'"
read CX2 <<< $( echo ${CX} | eval sed "$SED_ARG")
read CY2 <<< $( echo ${CY} | eval sed "$SED_ARG")
read CZ2 <<< $( echo ${CZ} | eval sed "$SED_ARG")
DIREC="${CX2}_${CY2}_${CZ2}"
echo $DIREC
cd "$DIREC"
the value of variable DIREC is the name of a directory and I get things like
m25.1240_m22.1250_m5.1540
this directory does exist, and if I do directly in bash cd m25.1240_m22.1250_m5.1540 it works and I can get inside. But on the script it does not work and I get the error:
: No such file or directory: cd: m25.1240_m22.1250_m5.1540
I do not understand why the error
PS:
echo "$DIREC" | od -c
gives
0000000 m 2 5 . 1 2 4 0 _ m 2 2 . 1 2 5
0000020 0 _ m 5 . 1 5 4 0 \r \n
0000033
Does your RESULTS_${lach}tal2 file have windows-style line endings? Does CZ end with a carriage return? What does this show:
echo "$DIREC" | od -c
Additionally, there's a lot of unnecessary eval'ing going on. Bash can do replacements in variable substitution:
read ENE CX CY CZ <<< $(head -n 1 RESULTS_${lach}tal2 | sed 's/\r$//')
DIREC="${CX/-/m}_${CY/-/m}_${CZ/-/m}"
I suspect that inside the script, your working directory is elsewhere, thus you cannot cd. Try this: instead of
cd "$DIREC"
replace it with
echo current directory is $PWD
cd "m25.1240_m22.1250_m5.1540"
and see if you still have the same problem.