Multiple sub-string matches using process substitution - bash

I want to do sub-string match within .sh file and extract list of cmds and append it an existing file.
So far I'm using perl -ne and it works with last-matched value shown on screen as
echo 'declare -a org_val=($(chkconfig --list autofs) $(grep "^PROMPT="/etc/sysconfig/init)' |
perl -pe 's|.*\$\((.*?)\)\s+|\1|g'
The following is the output of above command:
grep `"^PROMPT=" /etc/sysconfig/init`
I also want it to output
chkconfig --list autofs
What I did was write a small .sh to and save results of sed in array using command substitution below
#!/bin/bash
nl='\n'
declare -a array0
while IFS=$nl read -r line
do
array0+=$line
#echo $line
done < <( perl -pe 's|.*\$\((.*?)\)\s+|\1|g') < /tmp/sunny
echo "${array0[#]}"
The output of above is
declare -a org_val=($(chkconfig --list autofs) $(grep "^PROMPT=" /etc/sysconfig/init)
content of /tmp/sunny is
declare -a org_val=($(chkconfig --list autofs) $(grep "^PROMPT=" /etc/sysconfig/init) )

perl -nle'print for /\$\(([^)]*)\)/g'
Of course, that assumes that no ) exists within $(...).

With GNU awk for multi-char RS:
$ echo 'declare -a org_val=($(chkconfig --list autofs) $(grep "^PROMPT="/etc/sysconfig/init)' |
awk -v RS='[$][(][^()]+' 'RT{print substr(RT,3)}'
chkconfig --list autofs
grep "^PROMPT="/etc/sysconfig/init

Related

OS version capture script - unexpected results when using awk

I have a small shell script as follows that I am using to login to multiple servers to capture whether the target server is using Redhat or Ubuntu as the OS version.
#!/bin/ksh
if [ -f $HOME/osver.report.txt ];then
rm -rf $HOME/osver.report.txt
fi
for x in `cat hostlist`
do
OSVER=$(ssh $USER#${x} "cat /etc/redhat-release 2>/dev/null || grep -i DISTRIB_DESCRIPTION /etc/lsb-release 2>/dev/null")
echo -e "$x \t\t $OSVER" >> osver.report.txt
done
The above script works, however, if I attempt to add in some awk as shown below and the server is a redhat server...my results in the osver.report.txt will only show the hostname and no OS version. I have played around with the quoting, but nothing seems to work.
OSVER=$(ssh $USER#${x} "cat /etc/redhat-release | awk {'print $1,$2,$6,$7'} 2>/dev/null || grep -i DISTRIB_DESCRIPTION /etc/lsb-release 2>/dev/null")
If I change the script as suggested to the following:
#!/bin/bash
if [ -f $HOME/osver.report.txt ];then
rm -rf $HOME/osver.report.txt
fi
for x in cat hostlist
do
OSVER=$(
ssh $USER#${x} bash << 'EOF'
awk '{print "$1,$2,$6,$7"}' /etc/redhat-release 2>/dev/null || grep -i DISTRIB_DESCRIPTION /etc/lsb-release 2>/dev/null
EOF
)
echo -e "$x \t\t $OSVER" >> osver.report.txt
done
Then I get the following errors:
./test.bash: line 9: unexpected EOF while looking for matching `)'
./test.bash: line 16: syntax error: unexpected end of file
You're suffering from a quoting problem. When you pass a quoted command to ssh, you effectively lose one level of quoting (as if you passed the same arguments to sh -c "..."). So the command that you're running on the remote host is actually:
cat /etc/redhat-release | awk '{print ,,,}' | grep -i DISTRIB_DESCRIPTION /etc/lsb-release
One way of resolving this is to pipe your script into a shell, rather than passing it as arguments:
OSVER=$(
ssh $USER#${x} bash <<'EOF'
awk '{print "$1,$2,$6,$7"}' /etc/redhat-release 2>/dev/null ||
grep -i DISTRIB_DESCRIPTION /etc/lsb-release 2>/dev/null
EOF
)
The use of <<'EOF' here inhibits any variable expansion in the here document...without that, expressions like $1 would be expanded locally.
A better solution would be to look into something like ansible which has built-in facilities for sshing to groups of hosts and collecting facts about them, including distribution version information.

sed or awk to append to specific line

There is a file that I'd like to edit from a bash script
if [[ -n "$CHROME_USER_DATA_DIR" ]]; then
exec -a "$0" "$HERE/chrome" \
--user-data-dir="$CHROME_USER_DATA_DIR" "$#"
else
exec -a "$0" "$HERE/chrome" "$#"
fi
I would like to append to the end of any line containing exec -a "$0" "$HERE/chrome" "$#" the string
--user-data-dir
The result would over write the existing file with
if [[ -n "$CHROME_USER_DATA_DIR" ]]; then
exec -a "$0" "$HERE/chrome" \
--user-data-dir="$CHROME_USER_DATA_DIR" "$#"
else
exec -a "$0" "$HERE/chrome" "$#" --user-data-dir
fi
I am having difficulty understanding sed and awk, but want to make this work.
Unfortunately you need to use a regexp instead of a string comparison since your white space can apparently vary so that introduces some escaping complexity. Using GNU awk for -i inplace and \s shorthand for [[:space:]]:
awk -i inplace '/exec\s+-a\s+"\$0"\s+"\$HERE\/chrome"\s+"\$#"/{$0=$0 " --user-data-dir"} 1' file
Try this to edit your file with GNU sed:
sed -i '5s/$/& --user-data-dir/' file
With awk you could do:
awk 'NR==5{$0=$0" --user-data-dir"}1' file > newfile

Ubuntu BASH inotifywait to trigger another script

I am trying to use inotifywait within a bash script to monitor a directory for a file with a certain tag in it (*SDS.csv).
I also only want to execute once (once when the file is written to the directory data ).
example:
#! /bin/bash
inotifywait -m -e /home/adam/data | while read LINE
do
if [[ $LINE == *SDS.csv ]]; then
./another_script.sh
fi
done
While this may not be the ideal solution, it may do the trick:
#! /bin/bash
while true
do
FNAME="$(inotifywait -e close_write /home/adam/data | awk '{ print $NF }')"
if [ -f "/home/adam/data/$FNAME" ]
then
if grep -q 'SDS.csv' "/home/adam/data/$FNAME"
then
./another_script.sh
fi
done
done

How do I store a bash command as string for multiple substitutions?

I'm trying to clean up this script I have and this piece of code is annoying me because I know it can be more DRY:
if grep --version | grep "GNU" > /dev/null ;
then
grep -P -r -l "\x0d" $dir | grep "${fileRegex}"
else
grep -r -l "\x0d" $dir | grep "{$fileRegex}"
fi
My thoughts are to somehow conditionally set a string variable to either "grep -P" or "egrep" and then in a single line do something like:
$(cmdString) -r -l "\x0d" $dir | grep "${fileRegex}"
Or something like that but it doesn't work.
Are you worried about a host which has GNU grep but not egrep? Do such hosts exist?
If not why not just always use egrep? (Though -P and egrep are not the same thing.)
That being said you don't use strings for this (see BashFAQ#50).
You use arrays: grepcmd=(egrep) or grepcmd=(grep -P) and then "${grepcmd[#]}" ....
You can also avoid needing perl mode entirely if you use $'\r' or similar (assuming your shell understands that quoting method).
You can do this:
if grep --version | grep "GNU" > /dev/null
then
cmdString=(grep -P)
else
cmdString=(egrep)
fi
"${cmdString[#]}" -r -l "\x0d" "$dir" | grep "{$fileRegex}"
#Etan Reisner's suggestion worked well. For those that are interested in the final code (this case is for tabs, not windows line endings but it is similar):
fileRegex=${1:-".*\.java"}
if grep --version | grep "GNU" > /dev/null ;
then
cmdString=(grep -P)
else
cmdString=(grep)
fi
arr=$("${cmdString[#]}" -r -l "\x09" . | grep "${fileRegex}")
if [ -n "$dryRun" ]; then
for i in $arr; do echo "$i"; done
else
for i in $arr; do expand -t 7 "$i" > /tmp/e && mv /tmp/e "$i"; done
fi

Replace script works if I type manually but not in script

I have a bash script, replace.sh with the following contents:
ack-grep -a -l -i --print0 --text "$1" | xargs -0 -n 1 sed -i -e 's/$1/$2/g'
When I try and run it as, eg:
replace.sh something somethingnew
The prompt returns without errors but no changes have been made to any files.
If I manually type:
ack-grep -a -l -i --print0 --text "something" | xargs -0 -n 1 sed -i -e 's/something/somethingelse/g'
The files get changed as expected.
Ths $1 syntax seems to work for other scripts I've written. I'm guessing I'm missing something to do with escaping the args or something?
Thanks!
Ludo.
Variable substitutions aren't done in single quotes, try:
ack-grep -a -l -i --print0 --text "$1" | xargs -0 -n 1 sed -i -e "s/$1/$2/g"
See the bash man page section on QUOTING.
Use "" instead of '' in the sed expression. It will not prevent the variablename-resolving. What you are actually doing now is replacing $1 to $2. You can test in console (without writing a script) like this:
$ a=something
$ b=somethingelse
$ sed 's/$a/$b/g' testfile
$ sed "s/$a/$b/g" testfile
This isn't related to your question, but some help on using ack.
The -a and --text conflict with each other. -a will give you a superset of --text. Use one or the other.
Also, it looks like you might as well use grep -Z instead of ack since you're not using any of ack's functionality that is a superset of grep.
In general, if you're using ack in a pipeline, you should probably be using good ol' grep instead.

Resources