Bash: Keeping indentation during interpolation - bash

I have a variable containing a multiline string.
I am going to interpolate this variable into another multiline echoed string, this echoed string has indentation.
Here's an example:
ip_status=`ip -o addr | awk 'BEGIN { printf "%-12s %-12s %-12s\n", "INTERFACE", "PROTOCOL", "ADDRESS"
printf "%-12s %-12s %-12s\n", "---------", "--------", "-------" }
{ printf "%-12s %-12s %-12s\n", $2, $3, $4 }'`
echo -e "
->
$ip_status
->
"
When running that, the first line of $ip_status is left justified against the ->, however the subsequent lines are not justified against the ->.
It's easier to see if you run that in your bash. This is the output:
->
INTERFACE PROTOCOL ADDRESS
--------- -------- -------
lo inet 127.0.0.1/8
lo inet6 ::1/128
eth0 inet 10.0.2.15/24
eth0 inet6 fe80::a00:27ff:fed3:76c/64
->
I want all the lines in the $ip_status to be aligned with the ->, not just the first line.

You need to insert the indentation yourself. Bash comes with no feature for making text pretty, although there are some possibly useful utilities (column -t is frequently useful in this sort of application, for example).
Still, inserting indentation isn't too difficult. Here's one solution:
echo "
->
${ip_status//$'\n'/$'\n '}
->
"
Note: I removed the non-standard -e flag because it really isn't necessary.
Another alternative would be to apply the replacement on the entire output, using a tool like sed:
echo "
->
$ip_status
->
" | sed 's/^ */ /'
This second one has the possible advantage that it will tidy up the indentation, even if it were ragged as in the example. If you didn't want that effect, use 's/^/ /' instead.
Or a little shell function whose first argument is the desired indent and whose remaining arguments are indented and concatenated with a newline after each one:
indent() {
local s=$(printf '%*s' $1 "")
shift
printf "$s%s\n" "${#//$'\n'/$'\n'$s}"
}
indent 4 '->' "$ip_status" '->'
That might require some explanation:
printf accepts * as a length specifier, just like the C version. It means "use the corresponding argument as the numeric value". So local s=$(printf '%*s' $1 "") creates a string of spaces of length $1.
Also, printf repeats its format as often as necessary to consume all arguments. So the second printf applies an indent at the beginning and a newline at the end to each argument.
"${#/pattern/subst}" is a substitution applied to each argument in turn. Using two slashes at the beginning ("${#//pattern/subst}") makes it a repeated substitution.
$'\n' is a common syntax for interpreting C-style backslash escapes, implemented by bash and a variety of other shells. (But it's not available in a minimal posix standard shell.)
So "${#//$'\n'/$'\n'$s}" inserts $s -- that is, the desired indentation -- after every newline in each argument.

echo " ->"
while IFS= read -r line
do
echo " $line"
done <<< "$ip_status"
echo " ->"
You can read the variable line by line and echo it with the number of spaces you need before it. I have used the accepted answer of this question.
To make it a function:
myfunction() {
echo " ->"
while IFS= read -r line
do
echo " $line"
done <<< "$1"
echo " ->"
}
myfunction "$ip_status"

A simple form is to use readarray, process substitution and printf:
readarray -t ip_status < <(exec ip -o addr | awk 'BEGIN { printf "%-12s %-12s %-12s\n", "INTERFACE", "PROTOCOL", "ADDRESS"
printf "%-12s %-12s %-12s\n", "---------", "--------", "-------" }
{ printf "%-12s %-12s %-12s\n", $2, $3, $4 }')
printf ' %s\n' '->' "${ip_status[#]}" '->'
Reference: http://www.gnu.org/software/bash/manual/bashref.html

Related

Bash loop on files in folder without specific pattern

I have to cycle over the files present in a folder but I dont want to cycle over files with a specific pattern ("Reverse"). Here is the code
Thanks
DIRECTORY=/Users/Qi.Wang/projects/CH12F3/data/CH12F3.LAM-HTGTS_mMYC.220512
outputDir=/Users/Qi.Wang/projects/CH12F3/
pw=$(pwd)
cat blank.txt > ./config.txt
c="$pw/config.txt"
o=0
for i in $DIRECTORY/*.fna; do
((o=o+1))
s=${i##*/}
b=${s%.fna}
b="${o}_$(echo $b | awk '{ gsub(/_PairEnd+/, " " ); print $1 }')"
outputDirs="$outputDir$b"
printf "%s\t" $b >> ./config.txt
printf "%s\t" $s >> ./config.txt
cat end.txt >> ./config.txt
printf "perl /Users/andy/projects/HTGTS/pipeline/align_tools/TLPpipeline.pl %s %s which=%s assembly=mm9 blatopt=mask=lower outdir=/%s -skipred -skipredadd -skipblu -skipbluadd \n" $c $DIRECTORY $o $outputDirs >> ./command.sh
done
I Also have another minor problem. When i printf outdirs=%s the variable that is printed is $outputDir that starts with a "/" but after it got printed by printf, looks like the / is not there anymore.
Your awk command puts spaces $b, so $outputDirs will contain spaces. Therefore, you need to quote it to make it a single argument to printf. You should also quote all the other variable arguments.
Also, since you're creating a perl command line, you'll want outdir=%s to be a single argument, so you should put single quotes around that as well.
printf "perl /Users/andy/projects/HTGTS/pipeline/align_tools/TLPpipeline.pl '%s' '%s' 'which=%s' assembly=mm9 blatopt=mask=lower 'outdir=/%s' -skipred -skipredadd -skipblu -skipbluadd \n" "$c" "$DIRECTORY" "$o" "$outputDirs" >> ./command.sh
To skip files with Reverse in the name, enable extended globbing and use a non-matching pattern.
shopt -s extglob
for i in "$DIRECTORY"/!(*Reverse*).fna; do

How to parse multiple line output as separate variables

I'm relatively new to bash scripting and I would like someone to explain this properly, thank you. Here is my code:
#! /bin/bash
echo "first arg: $1"
echo "first arg: $2"
var="$( grep -rnw $1 -e $2 | cut -d ":" -f1 )"
var2=$( grep -rnw $1 -e $2 | cut -d ":" -f1 | awk '{print substr($0,length,1)}')
echo "$var"
echo "$var2"
The problem I have is with the output, the script I'm trying to write is a c++ function searcher, so upon launching my script I have 2 arguments, one for the directory and the second one as the function name. This is how my output looks like:
first arg: Projekt
first arg: iseven
Projekt/AX/include/ax.h
Projekt/AX/src/ax.cpp
h
p
Now my question is: how do can I save the line by line output as a variable, so that later on I can use var as a path, or to use var2 as a character to compare. My plan was to use IF() statements to determine the type, idea: IF(last_char == p){echo:"something"}What I've tried was this question: Capturing multiple line output into a Bash variable and then giving it an array. So my code looked like: "${var[0]}". Please explain how can I use my line output later on, as variables.
I'd use readarray to populate an array variable just in case there's spaces in your command's output that shouldn't be used as field separators that would end up messing up foo=( ... ). And you can use shell parameter expansion substring syntax to get the last character of a variable; no need for that awk bit in your var2:
#!/usr/bin/env bash
readarray -t lines < <(printf "%s\n" "Projekt/AX/include/ax.h" "Projekt/AX/src/ax.cpp")
for line in "${lines[#]}"; do
printf "%s\n%s\n" "$line" "${line: -1}" # Note the space before the -1
done
will display
Projekt/AX/include/ax.h
h
Projekt/AX/src/ax.cpp
p

How to print "-" using printf

I am trying to print "-" multiple times using printf. I am using the below command to print the same character multiple times, which works fine for all except "-".
printf "`printf '=%.0s' {1..30}` \n"
When I try to do the same for "-", it gives error.
printf "`printf '-%.0s' {1..30}` \n"
bash: printf: -%: invalid option
It is trying to take it as user-passed option. How do I work around this?
Pass -- before everything else to each printf invocation:
printf -- "`printf -- '-%.0s' {1..30}` \n"
Like many commands, printf takes options in the form of tokens starting with - (although -v and -- are the only options I know). This interferes with your argument string, as printf is instead trying to parse -%.0s as an option. For that case however, it supports the -- option (like many other commands), which terminates option parsing and passes through all following arguments literally.
Are you trying to print 30 hyphens? This is how I do that:
printf "%*s\n" 30 "" | sed 's/ /-/g'
The printf command prints a line with 30 spaces, then use sed to turn them all into hyphens
This can be encapsulated into a function:
ruler() { printf "%*s\n" "$1" "" | sed "s/ /${2//\//\\\/}/g"; }
And then you can do stuff like:
ruler $(tput cols) =

how to prevent for loop from using space as deliminator, bash script

I am trying to right a bash script to do multiple checks and searches for a CMS my company uses. I trying to implement a function for a user to be able to search for a certain macro call and the function return all the files that contain the call, the line the macro is called on, and the actual code in the macro call. What I have seems to be getting screwed up by the fact I am using a for loop to format the output. Here's the snippet of the script I am working on:
elif [ "$choice" = "2" ]
then
echo -e "\n What macro call are we looking for $name?"
read macrocall
for i in $(grep -inR "$macrocall" $sitepath/templates/macros/); do
file=$(echo $i | cut -d\: -f1 | awk -F\/ '{ print $NF }')
line=$(echo $i | cut -d\: -f2)
calltext=$(echo $i | cut -d\: -f3-)
echo -e "\nFile: $file"
echo -e "\nLine: $line"
echo -e "\nMacro Call from file: $calltext"
done
fi
the current script runs the first few fields until it gets a a space and then everything gets all screwy. Anybody have any idea how I can have the for loops deliminator to be each result of the grep? any suggestions would be helpful. Let me know if any of you need more info. Thanks!
The right way to do this would be more like:
printf "\n What macro call are we looking for %s?" "$name"
read macrocall
# ensure globbing is off and set IFS to a newline after saving original values
oSET="$-"; set -f; oIFS="$IFS"; IFS=$'\n'
awk -v macrocall="$macrocall" '
BEGIN { lc_macrocall = "\\<" tolower(macrocall) "\\>" }
tolower($0) ~ lc_macrocall {
file=FILENAME
sub(/.*\//,"",file)
printf "\n%s\n", file
printf "\n%d\n", FNR
printf "\nMacro Call from file: %s\n", $0
}
' $(find "$sitepath/templates/macros" -type f -print)
# restore original IFS and globbing values
IFS="$oIFS"; set +f -"$oSET"
This solves the problem of having spaces in your file names as originally requested, but also handles globbing characters in your file names, and the various typical echo issues.
You can set the internal field separator $IFS (which is normally set to space, tab and newline) to just newline to get around this problem:
IFS="\n"

How to get output of grep in single line in shell script?

Here is a script which reads words from the file replaced.txt and displays the output each word in each line, But I want to display all the outputs in a single line.
#!/bin/sh
echo
echo "Enter the word to be translated"
read a
IFS=" " # Set the field separator
set $a # Breaks the string into $1, $2, ...
for a # a for loop by default loop through $1, $2, ...
do
{
b= grep "$a" replaced.txt | cut -f 2 -d" "
}
done
Content of "replaced.txt" file is given below:
hllo HELLO
m AM
rshbh RISHABH
jn JAIN
hw HOW
ws WAS
ur YOUR
dy DAY
This question can't be appropriate to what I asked, I just need the help to put output of the script in a single line.
Your entire script can be replaced by:
#!/bin/bash
echo
read -r -p "Enter the words to be translated: " a
echo $(printf "%s\n" $a | grep -Ff - replaced.txt | cut -f 2 -d ' ')
No need for a loop.
The echo with an unquoted argument removes embedded newlines and replaces each sequence of multiple spaces and/or tabs with one space.
One hackish-but-simple way to remove trailing newlines from the output of a command is to wrap it in printf %s "$(...) ". That is, you can change this:
b= grep "$a" replaced.txt | cut -f 2 -d" "
to this:
printf %s "$(grep "$a" replaced.txt | cut -f 2 -d" ") "
and add an echo command after the loop completes.
The $(...) notation sets up a "command substitution": the command grep "$a" replaced.txt | cut -f 2 -d" " is run in a subshell, and its output, minus any trailing newlines, is substituted into the argument-list. So, for example, if the command outputs DAY, then the above is equivalent to this:
printf %s "DAY "
(The printf %s ... notation is equivalent to echo -n ... — it outputs a string without adding a trailing newline — except that its behavior is more portably consistent, and it won't misbehave if the string you want to print happens to start with -n or -e or whatnot.)
You can also use
awk 'BEGIN { OFS=": "; ORS=" "; } NF >= 2 { print $2; }'
in a pipe after the cut.

Resources