ZSH script exits even though ERR_EXIT is off - while-loop

I have this pipeline in my shell script. It's meant to prompt the user to select PulseAudio sources to record with ffmpeg, but to the best of my knowledge, that doesn't matter for this specific issue.
integer num_sources=0
pactl list short sources \
| while IFS= read -r line; do if [[ "$line" =~ '^([[:digit:]]+)\s+(\S+)' ]]; then
echo "#$match[1]: $match[2]"
fi; done \
| (fzf -m --prompt='Select audio source: ' || true) \
| while IFS= read -r src_line; do if [[ "$src_line" =~ '^#([[:digit:]]+)' ]]; then
local pulsesrc="$match[1]"
(( num_sources++ ))
ffpulseopts+=(
-f pulse
-i "$pulsesrc"
)
fi; done
My issue is that, if I remove the || true from the fzf invocation, leaving fzf by means of pressing Escape, which causes a non-zero exit code, will cause the entire script to terminate. I don't understand why this happens, even though neither the ERR_EXIT nor the ERR_RETURN options are set. PIPE_FAIL is not set either, though I'm not sure how it would come into play here.
λ unsetopt | egrep '^(err|pipe)'
errexit
errreturn
pipefail
I have verified this issue with shorter examples as well; the following snippet will not print foo if you exit fzf with Escape. This doesn't happen when fzf is replaced with false, so I was able to trace the issue back to the exit code, which is 130 (indicating SIGINT) when pressing Escape.
λ (echo a; echo b; echo c) | fzf | while IFS= read -r line; do echo $line; done; echo foo
λ (echo a; echo b; echo c) | false | while IFS= read -r line; do echo $line; done; echo foo
foo
λ (echo a; echo b; echo c) | (exit 130) | while IFS= read -r line; do echo $line; done; echo foo
However, if you remove the loop, it will print as expected.
λ (echo a; echo b; echo c) | (exit 130); echo foo
foo
Even when you add other parts to the pipeline, it will still continue to print foo properly
λ (echo a; echo b; echo c) | (exit 130) | false; echo foo
foo
λ (echo a; echo b; echo c) | (exit 130) | true; echo foo
foo
After some fiddling, I found out that it's not just the loop either. It only happens when a middle part of the pipe exits with SIGINT AND the loop is the last part of the pipeline.
λ (exit 130) | while IFS= read -r line; do echo $line; done; echo foo
foo
λ false | (exit 130) | while IFS= read -r line; do echo $line; done; echo foo
λ true | (exit 130) | while IFS= read -r line; do echo $line; done; echo foo
λ false | (exit 130) | while IFS= read -r line; do echo $line; done | false; echo foo
foo
λ true | (exit 130) | while IFS= read -r line; do echo $line; done | false; echo foo
foo
λ false | (exit 130) | false; echo foo
foo
I'm stumped here, and don't really know where to go without a hack like || true (|| false works as well as it turns the SIGINT exit code into a "regular" error exit code).
Edit: Upon request, here is the output of setopt.
λ setopt
alwaystoend
autocd
autopushd
completeinword
extendedglob
extendedhistory
noflowcontrol
histexpiredupsfirst
histignoredups
histignorespace
histverify
incappendhistory
interactive
interactivecomments
kshglob
longlistjobs
monitor
promptsubst
pushdignoredups
pushdminus
sharehistory
shinstdin
zle
Edit 2: Some system context upon request
I am on an up-to-date (as of May 2nd, 2020, 19:04) Arch Linux, with ZSH 5.8.

Related

How can I access a VPN inside a VMWare Fusion VM

I have a VPN connection in MacOS BigSur but I can't access it inside a Linux VM running under VMWare Fusion V12.1.2.
The issue has been fixed in V12.2.0 VMWare Fusion 12.2.0 Release Notes
The solution is to manually create the VPN tunnel and link it to the VM as there are multiple commands involved and the IP Address can change I created the following script to execute the required commands.
#!/bin/bash
function ask_yes_or_no() {
read -p "$1 ([y]es or [N]o): "
case $(echo $REPLY | tr '[A-Z]' '[a-z]') in
y|yes) echo "yes" ;;
*) echo "no" ;;
esac
}
currNatRules=$(sudo pfctl -a com.apple.internet-sharing/shared_v4 -s nat 2>/dev/null)
if test -z "$currNatRules"
then
echo -e "\nThere are currently no NAT rules loaded\n"
exit 0
fi
utunCheck=$(echo $currNatRules | grep utun)
if test -n "$utunCheck"
then
echo -e "\nIt looks like the VPN tunnel utun2 has already been created"
echo -e "\n$currNatRules\n"
if [[ "no" == $(ask_yes_or_no "Do you want to continue?") ]]
then
echo -e "\nExiting\n"
exit 0
fi
fi
natCIDR=$(echo $currNatRules | grep en | grep nat | cut -d\ -f 6)
if test -z "$natCIDR"
then
echo -e "\nCannot extract the NAT CIDR from:"
echo -e "\n$currNatRules\n"
exit 0
fi
interface=$(route get 10/8 | grep interface | cut -d\ -f 4)
echo -e "\nNAT CIDR=$natCIDR Interface=$interface\n"
newRule="nat on ${interface} inet from ${natCIDR} to any -> (${interface}) extfilter ei"
echo -e "\nAdding new rule: $newRule\n"
configFile="fixnat_rules.conf"
[[ -d $configFile ]] && rm $configFile
echo "$currNatRules" > $configFile
echo "$newRule" >> $configFile
sudo pfctl -a com.apple.internet-sharing/shared_v4 -N -f ${configFile} 2>/dev/null
echo -e "\nConfig update applied\n"
sudo pfctl -a com.apple.internet-sharing/shared_v4 -s nat 2>/dev/null
echo -e "\n"
exit 0

shell script to perform stop start multiple httpd instances

I want to write a script to do restart of httpd instances only if it is in running status. For ine instance it is working fine, but more than one instance it is failing.
below is script which I am using:
ctl_var=`find /opt/apache/instances/ -name apachectl | grep -v "\/httpd\/"`
ctl_proc=`ps -ef | grep -i httpd | grep -i " 1 " wc -l`
if [ $ctl_proc <= 0 ];
then echo "httpd is not running";
else $ctl_var -k stop; echo "httpd stopped successfully" ;
sleep 5;
$ctl_var -k start;
sleep 5;
echo "httpd started" ps -ef | grep httpd | grep -i " 1 ";
fi
Please suggest...
You mentioned there are multiple instances, i see it misses for loop on execution of script. Here it only restarts the last one picked in the $ctl_var
Modified script should look something like below, tweak script if necessary :
ctl_var=`find /opt/apache/instances/ -name apachectl | grep -v "\/httpd\/"`
ctl_proc=`ps -ef | grep -i httpd | grep -i " 1 " wc -l`
for i in `echo $ctl_var`
do
if [ $ctl_proc <= 0 ];
then echo "httpd is not running";
else $i -k stop; echo "httpd stopped successfully" ;
sleep 5;
$i -k start;
sleep 5;
echo "httpd started" ps -ef | grep httpd | grep -i " 1 ";
fi
done
Hope this helps.

Modify zsh commands to forward errors

I would like to modify one of my recent Bash aliases to forward errors. Here is the alias:
alias makecclip=
"make |& tee >(sed \"s,\x1B\[[0-9;]*[a-zA-Z],,g\" |
egrep \":[0-9]+:[0-9]+: error\" | cut -d : -f1,2,3 |
head -n 1 | xargs -0 echo -n | xclip -selection clipboard &&
xclip -selection clipboard -o)
This code displays the results of a C++ compilation, and then removes formatting and displays and adds to the clipboard the first error location (if there is any).
However, I would like to use this code like this:
makecclip && bin/someexecutablecreated
This though ruins the && operator, since it always runs bin/someexecutablecreated even when there is a compilation error present. How can I add modifications to the code to set the error flag, when the error list (the things saved to clipboard and echoed) is not empty?
You can address your issue by using the PIPESTATUS internal variable (this variable has other names in non-bash shells). This allows to have an history of exit statuses of commands passed by pipe.
You precised in the comments that you didn't use bash, but used zsh instead. As such, some of the syntax of my solution has to be changed, as they handle the PIPESTATUS variable differently.
In bash, you use ${PIPESTATUS[0]}, whereas you'll use ${pipestatus[1]} in zsh.
A first approach, using your existing alias, could be as follow :
makecclip && [ "${pipestatus[1]}" -eq "0" ] && echo "ok"
This runs the echo command only if "${pipestatus[1]}" is equal to 0 (no errors during make)
A more convenient solution would be to use a function instead of an alias for makecclip. In your ~/.bashrc file, you could write :
makecclip () {
make |& tee >(sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" | egrep ":[0-9]+:[0-9]+: error" | cut -d : -f1,2,3 | head -n 1 | xargs -0 echo -n | xclip -selection clipboard && xclip -selection clipboard -o)
return "${pipestatus[1]}"
}
Now, makecclip && echo "ok" will work as expected.
Test cases :
#!/bin/zsh
#do not run this test if there is an existing makefile in your current directory
rm -f makefile
makecclip () {
make |& tee >(sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" | egrep ":[0-9]+:[0-9]+: error" | cut -d : -f1,2,3 | head -n 1 | xargs -0 echo -n | xclip -selection clipboard && xclip -selection clipboard -o)
# this part is only present to check the pipestatus values during the tests.
# In the real function, I wrote 'return ${pipestatus[1]}' instead.
a=(${pipestatus[#]})
echo ${a[#]}
return ${a[1]}
}
echo "# no makefile"
makecclip && echo "ok"
echo -e "\n# empty makefile"
touch makefile
makecclip && echo "ok"
echo -e "\n# dummy makefile entry"
echo -e 'a:\n\t#echo "inside makefile"' > makefile
makecclip && echo "ok"
echo -e "\n# program with error makefile"
echo -e "int main(){error; return 0;}" > target.cc
echo -e 'a:\n\tgcc target.cc' > makefile
makecclip && echo "ok"
Output :
$ ./test.sh
# no makefile
make: *** No targets specified and no makefile found. Stop.
2 0
# empty makefile
make: *** No targets. Stop.
2 0
# dummy makefile entry
inside makefile
0 0
ok
# program with error
gcc target.cc
target.cc: In function ‘int main()’:
target.cc:1:12: error: ‘error’ was not declared in this scope
int main(){error; return 0;}
^
makefile:2: recipe for target 'a' failed
make: *** [a] Error 1
target.cc:1:12
2 0

AccuRev: how do you list the managed files?

I need to see which files have been added or removed between two streams. The most obvious way would be "git lsfiles" in each stream. Except this is not GIT and I do not see an analogous command. So for today:
for f in $(find * -type f);do
accurev stat "$f"
done | \
fgrep -v '(external)' | \
awk '{print $1}' > .list
If there is a better way, it should be clear and easy to find here:
http://www.accurev.com/download/docs/5.7.0_books/AccuRev_5_7_User_CLI.pdf
but it is not. Help? Thank you.
If you want to see the difference between two streams, run the following command: accurev diff -a -v "Stream1" -V "Stream2"
As the command line question has been answered, here's how to do the same via the AccuRev GUI.
Select one dynamic stream, workspace or snapshot.
Right click and select "Show Diff By Files"
Select a different dynamic stream, workspace or snapshot.
You'll be presented with a list of files different between the two choices, and yes you can mix-and-match between dynamic streams, workspaces and snapshots.
You can then select any file and select "Show Difference" to see differences between the two files.
Since neither of the two answers addressed the question, I eventually worked out a script to do what is really needed. "accurev lsfiles" is sorely needed.
#! /bin/bash
declare -r progpid=$$
declare -r progdir=$(cd $(dirname $0) >/dev/null && pwd)
declare -r prog=$(basename $0)
declare -r program="$progdir/$prog"
declare -r usage_text=' [ <directory> ... ]
If no directory is specified, "." is assumed'
die() {
echo "$prog error: $*"
exec 1>/dev/null 2>&1
kill -9 $progpid
exit 1
} 1>&2
usage() {
test $# -gt 0 && {
exec 1>&2
echo "$prog usage error: $*"
}
printf "USAGE: $prog %s\n" "$usage_text"
exit $#
}
init() {
shift_ct=$#
tmpd=$(mktemp -d ${TMPDIR:-/tmp}/ls-XXXXXX)
test -d "$tmpd" || die "mktemp -d does not work"
exec 4> ${tmpd}/files
trap "rm -rf '$tmpd'" EXIT
prune_pat=
while :
do
test $# -eq 0 && break
test -f "$1" && break
[[ "$1" =~ -.* ]] || break
case "X$1" in
X-p )
prune_pat+="${2}|"
shift 2 || usage "missing arg for '-p' option"
;;
X-p* )
prune_pat+="${1#-p}"
shift
;;
X-x* )
set -x
tput reset 1>&2
PS4='>${FUNCNAME:-lsf}> '
shift
;;
X-* )
usage "unknown option: $1"
;;
* )
break
;;
esac
done
(( shift_ct -= $# ))
test ${#prune_pat} -gt 0 && \
prune_pat='(^|/)('${prune_pat%|}')$'
}
chkdir() {
declare list=$(exec 2> /dev/null
for f in "$#"
do ls -d ${f}/* ${f}/.*
done | \
grep -v -E '.*/\.\.*$' )
for f in $(accurev stat ${list} | \
grep -v -F '(external)' | \
awk '{print $1}' | \
sed 's#^/*\./##')
do
test ${#prune_pat} -gt 0 && [[ $f =~ ${prune_pat} ]] && continue
if test -d "$f"
then chkdir "$f"
elif test -f "$f" -o -L "$f"
then echo "$f" 1>&4
fi
done
}
init ${1+"$#"}
(( shift_ct > 0 )) && shift ${shift_ct}
test $# -eq 0 && set -- .
chkdir "$#"
exec 4>&-
sort -u ${tmpd}/files
It is a bit over-the-top, but I have a boilerplate I always use for my scripts.

shell script not working

I have a shell script to do the following things
sudo as a user (johnsmith) and perform few things
Exit from that user and check url status
If status is not equal to 1 , ssh to one more server and execute a
script.
But when I am running it, the lines inside 'ENDBASH' are not getting executed at all.
#!/bin/ksh
echo "Outside ENDBASH ${###*/}"
sudo -u johnssmith bash <<'ENDBASH'
echo "Inside ENDBASH ${###*/}"
#Obtaining the new version file
for file in "${###*/}"
do
if echo "$file" | grep -E "abc_cde_efg"; then
echo "Version found: $file"
else
echo "Version not found"
fi
done
exit
ENDBASH
urlArray=('http://server:port/servicename1/services/servicename1?wsdl' 'http://server:port/servicename2/services/servicename2?wsdl')
status=0
for url in "${urlArray[#]}"
do
result=`curl -s $url`
if (echo $result | grep '<?xml' >/dev/null 2>&1); then
service=$(echo $url | cut -d"/" -f4)
echo "$service is Running"
else
service=$(echo $url | cut -d"/" -f4)
echo "$service is not Running"
status=1
fi
done
if [ $status != 1 ] ; then
ssh -t username#hostname /home/dev_was/test1.sh
fi
You need to explicitly pass the arguments received by your script to the internal script:
sudo -u johnssmith bash -s "$#" <<'ENDBASH'