How to preserve spaces in Bash "for loop" expression - bash

I have function that print lines with spaces and need to loop through the line with a for..in loop. How do I preserve the spaces and not tokenize based on them.
function getTwoThings
{
echo "A B C"
echo "X Y Z"
}
for L in `getTwoThings`
do
echo $L
done
for L in "`getTwoThings`"
do
echo $L
done
Produces either six things or one thing.
A
B
C
X
Y
Z
A B C X Y Z
How do I get it to produce two things?

Read it like this using process substitution:
while IFS= read -r line; do
echo "$line"
done < <(getTwoThings)
Output:
A B C
X Y Z

Related

Bash: tr command with variables representing chars

I want to use the tr command to map chars to new chars, for example:
echo "hello" | tr '[a-z]' '[b-za-b]' Will output: ifmmp
(where each letter in the lower-case alphabet is shifted over one to the right)
See below the mapping to new chars for '[b-za-b]':
[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] will map to:
[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 a]
However, when I want it to rotate multiple times, how can I use a variable to control the rotate-value for the tr command?
Eg: for a shift of 1:
echo "hello" | tr '[a-z]' '[b-za-b]' without variables and:
echo "hello" | tr '[a-z]' '[(a+$var)-za-(a+$var)]' where $var=1
here I have: (a+$var)-z representing the same as b-z and
....................a-(a+$var) representing the same as a-b
I have tried converting the ascii value to a char to use within the tr command but I don't think that is allowed.
My problem is that bash is not interpreting:
(a+$var) as the char b when $var=1
(a+$var) as the char c when $var=2
... etc.
How can I tell bash to interpret these equations as chars for the tr command
EDIT
I tried doing it with an array but it's not working:
chars=( {a..z} )
var=2
echo "hello" | tr '[a-z]' '[(${chars[var]}-z)(a-${chars[var]})]'
I used: (${chars[var]}-z) to represent b-z where var=1
Because ${chars[1]} is b but this is not working. Am I missing something?
What you are trying to do cannot be done using tr which does not handle your requirement. Moreover when you meant to modify and use variables to add to glob patterns in bash which is something you cannot possibly do.
There is a neat little trick you can do with bash arrays!. The tr command can take expanded array sequence over the plain glob patterns also. First define a source array as
source=()
Now add its contents as a list of character ranges from a-z using brace expansion as
source=({a..z})
and now for the transliterating array, from the source array, construct it as follows by using the indices to print the array elements
trans=()
Using a trick to get the array elements from the last with syntax ${array[#]: (-num)} will get you the total length - num of the elements. So building the array first as
var=2
trans+=( "${source[#]:(-(26-$var))}" )
and now to build the second part of the array, use another trick ${array[#]:0:num} to get the first num number of elemtents.
trans+=( "${source[#]:0:$(( $var + 1 ))}" )
So what we have done now is for a given value of var=2, we built the trans array as
echo "${trans[#]}"
c d e f g h i j k l m n o p q r s t u v w x y z a b c
Now you can just use it easily in the tr command
echo "hello" | tr "${source[*]}" "${trans[*]}"
jgnnq
You can just put it all in function and print its value as
transChar() {
local source
local trans
local result
source=({a..z})
trans=()
var="$2"
input="$1"
trans+=( "${source[#]:(-(26-$var))}" )
trans+=( "${source[#]:0:$(( $var + 1 ))}" )
result=$( echo "$input" | tr "${source[*]}" "${trans[*]}" )
echo "$result"
}
Some of the tests
transChar "hello" 1
ifmmp
transChar "hello" 2
jgnnq
transChar "hello" 3
khoor
rot-random:
# generate alphabet as arr:
arr=( {1..26} )
i=$(($RANDOM%24+1))
# left and right
l=$(echo ${arr[$i]})
r=$(echo ${arr[$i+1]})
# reusing arr for testing:
echo ${arr[#]} | tr "a-z" "$r-za-$l"
echo "secret:" | tr "a-z" "$r-za-$l" ; echo $l $r $i
amkzmb:
h i 7
You could use octal \XXX character codes for characters to do what you intend. Using the octal codes you could do any arithmetic manipulations to numbers and then convert them to character codes
# rotr x
#
# if 0 <= x <= 25 rotr x outputs a set specification
# that could be used as an argument to tr command
# otherwise it outputs 'a-z'
function rotr(){
i = $(( 97 + $1 ))
if [ $i -lt 97 ] ; then
translation='a-z'
elif [ $i -eq 97 ] ; then
translation='\141-\172' # 141 is the octal code for "a"
# 172 is the octal code for "z"
elif [ $i -eq 98 ] ; then
translation='\142-\172\141'
elif [ $i -lt 122 ] ; then # $i is between 99 and 121 ("c" and "y")
ii=$(echo "obase=8 ; $i" | bc)
jj=$(echo "obase=8 ; $(( $i - 1 ))" | bc)
translation=\\$ii'-\172\141-'\\$jj
elif [ $i -eq 122 ] ; then
translation='\172\141-\171'
else # $i > 122
tranlation='a-z'
fi
echo $translation
}
Now you could use this as follows
echo hello | tr 'a-z' $(rotr 7)
prints
olssv

How to pass items to bash for loop on multiple lines?

I can do this in bash:
for n in a b c d e ; do
echo $n
done
If a, b, c, d, e turn out to be long lines, without using a separate variable, how do I put them each on a separate line in the for loop syntax?
Split the line with \:
$ for i in a \
> b \
> c \
> d ; do echo $i ; done
a
b
c
d
I'd use a "here document":
while read n; do
echo $n
done <<EOF
some detailed stuff here
other things on the next line, blah blah blah
EOF
Of course in this particular example you can replace the entire while loop with cat but I suppose your real code is more involved.
you can put a b c d e in a file and run a loop over that file.
while read line
do
echo "$line \n"
done < file

How do I print a line of a shell-script in a shell-script?

I have a shell script that executes a command with a bunch of arguments that are, themselves, constructed by the script.
Eg. the script contains a line like this
do $opts x y z $more_opts a b c "bit in quotes" $stuff
I'd like to print out what this line actually is.
If I try :
echo 'do $opts x y z $more_opts a b c "bit in quotes" $stuff'
then I get the line with $opts and $more_opts being quoted literally rather than expanded into their values.
If I try backticks :
echo `do $opts x y z $more_opts a b c "bit in quotes" $stuff`
then it executes the line itself.
How can I generate and print a version of this string, with the $opts expanded into their values, but unexecuted?
The most accurate is:
printf '%q ' $opts x y z $more_opts a b c "bit in quotes" $stuff ; echo
since it will do the same word-splitting, filename-globbing, etc., as your actual command. (For example, if $stuff is *, this version will correctly list all the files in your directory, since that's what your real command will do.)
(Hat-tip to Gordon Davisson for suggesting printf + %q, so the final resulting arguments get re-quoted, instead of just printing them as-is.)
A better approach, though, might be what Jonathan Leffler suggests above:
(set -x; cmd $opts x y z $more_opts a b c "bit in quotes" $stuff)
which will tell Bash to debug-print the command before running it. That way you don't need to update your echo command every time you update your actual command.
While the solutions outlined here work, I would strongly advice against doing it, because a statement of the form
echo myprog $foo $bar $baz
myprog $foo $bar $baz
creates a maintenance problem: Whenever you modify the myprog .... line, you have to update the echo .... line in the same way.
A better solution would be to define a function
function echorun {
printf "EXECUTING:" # You can leave this out, if you want to
printf " %q" "$#" # Thank you, Gordon Davisson
echo # Supplying final newline
"$#"
}
and run the commands which you are wanting to log, with
echorun do $opts x y z $more_opts a b c "bit in quotes" $stuff

shell - put variable in ALPHABETICAL range of cycle

is there some way to put variable in ALPHABETICAL range of cycle?
This doesnt work.
read -p "Where I should start?" start #there will be entered one small letter
for aaa in {$start..z}; do #how put variable $start in range?
...
done
Thanks for reply.
Use eval to expand the variable:
$ s=t
$ eval echo {$s..z}
t u v w x y z
Your example then becomes:
read -p "Where I should start?" start #there will be entered one small letter
for aaa in $(eval echo {$start..z}); do
echo $aaa
done
Since you have user input to eval, you may want to check the value of start as being a single lower case character first:
read -p "Where I should start?" start #there will be entered one small letter
if [[ $start =~ ^[a-y]$ ]]; then
for aaa in $(eval echo {$start..z}); do
echo $aaa
done
else
echo "Need to use a letter 'a-y'"
fi
You can read more about Bash brace expansion here
Unfortunately, you can't put variables inside {start..end} ranges in bash.
This does what you want:
until [[ $s == "_" ]]; do echo $s && s=$(tr "a-z" "b-z_" <<<$s); done
It uses tr to translate each character to the next one. "_" is the character after "z".
For example:
$ s=t
$ until [[ $s == "_" ]]; do echo $s && s=$(tr "a-z" "b-z_" <<<$s); done
t
u
v
w
x
y
z
If you don't mind using Perl, you could use this:
perl -le 'print for shift .. "z"' $s
It uses .. to create a list between the first argument on the command line and "z".
A slightly more esoteric way to do it in bash would be:
for ((i=$(LC_CTYPE=C printf '%d' "'$s"); i<=122; ++i)); do
printf "\\$(printf '%03o' $i)\n"
done
The for loop goes from the ASCII character number of the variable $s to "z", which is ASCII character 122. The format specifier the inner printf converts the character number to octal, padding it with zeros up to three characters long. The outer printf then interprets this as an escape sequence and prints the character. Credit goes to Greg's wiki for the code used to convert ASCII characters to their values.
Of course you could just use eval to expand the variable, the advantage being that the code required to do so is much shorter. However, executing arbitrary strings that have been read in to your script is arguably a bit of a security hole.
x=t
for I in $(sed -nr "s/.*($x.*)/\1/;s/ /\n/g;p" <(echo {a..z}))
do
# do something with $I
done
Output:
t
u
v
w
x
y
z
I would avoid the use of eval.
for aaa in {a..z}; do
[[ $aaa < $start ]] && continue
...
done
The overhead of comparing $aaa to $start should be negligible, especially compare to the cost of starting a separate process to compute the range.

while loops offset in shell script

I/P file has data as follows:
Y
REQUIRES Z
A
REQUIRES B
C
REQUIRES D
REQUIRES E
REQUIRES F
G
REQUIRES H
I
REQUIRES J
EXACT OUTPUT FILE REQUIRED:
Y REQUIRES Z
A REQUIRES B
C REQUIRES D
C REQUIRES E
C REQUIRES F
G REQUIRES H
I REQUIRES J
I am using while loops to traverse the file.
while read line
do
if (condition)
{..
}
while read anoterline
do
done
done <inputfile
The problem I am facing is that
when inner while loop traverses say 4 lines and i break the inner
loop the outer while loop's offset is set to the offset at which the
inner while has stopped.
So I am missing the 4 lined data in execution of my outer loop.
I need the outer while loop to start off from the offset at which it had stopped
.
There's no reason to use nested loops here.
item=
while read -r first second _; do
if [[ $first = REQUIRES ]]; then
printf '%s REQUIRES %s\n' "$item" "$second"
else
item=$first
fi
done <inputfile
Invocation and output demonstrated at http://ideone.com/JejDyG
You can do this using awk
$ awk 'NF==1{a=$1};NF>1{print a,$0}' file
Y REQUIRES Z
A REQUIRES B
C REQUIRES D
C REQUIRES E
C REQUIRES F
G REQUIRES H
I REQUIRES J
Something like
#!/bin/bash
while read -r line; do
[[ $line =~ ^[A-Z]$ ]] && one="$line"
[[ $line =~ ^REQUIRES(.*) ]] && echo "$one $line"
done < "file"
Example
> ./abovescript
Y REQUIRES Z
A REQUIRES B
C REQUIRES D
C REQUIRES E
C REQUIRES F
G REQUIRES H
I REQUIRES J

Resources