I’m trying to figure out if there’s a way to do this more compactly (for reasons…):
T=$(openssl x509 -noout -text -in "$1") EXPD=$(date -d"$(echo "$T" | sed -n 's/^.*Not After : //p')" +%Y%b%d) SUBJ=$(echo "$T" | sed -n 's/^.*Subject: .*CN = //p')
I am having two issues:
1. Bash doesn’t happily let me embed double quotes inside a subshell inside a subshell inside double-quotes. All of the double-quotes are required, AFAICT. So doing X=$(…”$(…”xxx”)”) just doesn’t work because bash doesn’t parse nested double-quotes. But both the inner var and the result can and will have spaces in them. 2. I can’t remember how in shell (bash, in this case) how to bifurcate stdout and have it run to two pipelines. I figure echo is a builtin, so should at least be lighter than re-running the monstrosity that is openssl over and over. I have a vague recollection that this is harder than it sounds, and may not be worth it…
Any suggestions on how to solve either issue? I’m sure I’ve just forgotten some obvious technique, hopefully someone can jog my mind. -Adam
Not sure if this is it, but backticks.
eg.
EXPD=$(date -d"$(`$(openssl x509 -noout -text -in "$1")` | sed -n 's/^.*Not After : //p')" +%Y%b%d)
On 2023-02-26 18:07, Adam Thompson wrote:
I’m trying to figure out if there’s a way to do this more compactly (for reasons…):
T=$(openssl x509 -noout -text -in "$1")
EXPD=$(date -d"$(echo "$T" | sed -n 's/^.*Not After : //p')" +%Y%b%d)
SUBJ=$(echo "$T" | sed -n 's/^.*Subject: .*CN = //p')
I am having two issues:
- Bash doesn’t happily let me embed double quotes inside a
subshell inside a subshell inside double-quotes. All of the double-quotes are required, AFAICT. So doing X=$(…”$(…”xxx”)”) just doesn’t work because bash doesn’t parse nested double-quotes. But both the inner var and the result can and will have spaces in them.
- I can’t remember how in shell (bash, in this case) how to
bifurcate stdout and have it run to two pipelines. I figure echo is a builtin, so should at least be lighter than re-running the monstrosity that is openssl over and over. I have a vague recollection that this is harder than it sounds, and may not be worth it…
Any suggestions on how to solve either issue? I’m sure I’ve just forgotten some obvious technique, hopefully someone can jog my mind.
-Adam _______________________________________________ Roundtable mailing list Roundtable@muug.ca https://muug.ca/mailman/listinfo/roundtable
I can't see any way to "bifurcate output" in bash without using tee. I'm pretty sure zsh (and probably fish) can do it, but not sure of the syntax there either:
$ eval $((openssl x509 -noout -text -in /etc/pki/tls/certs/tecnopolis.ca.crt | tee >(echo -n EXPD="'"`date -d"$(sed -n 's/^.*Not After : //p')" +%Y%b%d`"'" ) >(echo SUBJ="'"`sed -n 's/^.*Subject: .*CN = //p'`"'" ) 1>&2 ) 2>/dev/null)
$ echo $SUBJ tecnopolis.ca $ echo $EXPD 2024Feb22
If you can figure out a way to get the vars out of the subshell and into 2 different vars without using the eval $() then you're probably better off. Even though I protect the tainted inputs with '', someone could possibly plant a ' in the SUBJ and thus this is a sec hole.
Of course you could eliminate the eval and assign to a var and then run a 2nd command to split them into SUBJ and EXPD, but I was going for a oneliner. (Get rid of the "'" adders then.)
I'm going to toy with the idea of using {} subshells which might allow elimination of the eval.
Of course this would be much cleaner in a perl oneliner... and if you're already bringing in sed, how much worse is perl?
Found my answer, sort of. Either use mkfiko and tee, or use bash/zsh/ksh88 process substitution a la "cmd >(subcmd1) >(subcmd2)", but I don't see any good way of getting the output from subcmd1/2 into variables as they run in subshells. It would be do-able by piping the whole thing into a "while read X" loop, but that's arguably getting into "the cure is worse than the disease" territory.
T=$(openssl x509 -noout -text -in "$1") EXPD=$(date -d"$(echo "$T" | sed -n 's/^.*Not After : //p')" +%Y%b%d) SUBJ=$(echo "$T" | sed -n 's/^.*Subject: .*CN = //p')
Untested as yet, but should work:
( openssl x509 -noout -text -in "$1" >(sed -n 's/^.*Not After : /A /p' | xargs date +%Y%b%d -d) >(sed -n 's/^.*Subject: .*CN = /B /p') ) | while read X; do case $X in A) EXPD="$X" ;; B) SUBJ="$X" ;; esac ; <do something with EXPD and SUBJ>
Talk about unreadable, though!
This does several things, as I understand it: 1. duplicate /dev/fd/1 (stdout) to, usually, /dev/fd/4 but it doesn't really matter which FD# 2. spawns the first subshell, and passes /dev/fd/1 as that subshell's stdin, and the subshell's stdout as the parent process's stdout 3. spawns the second subshell, and passes the dup'd FD (/dev/fd/4 or whatever it is) as that subshell's stdin, and the subshell's stdout as the parent process's stdout 4. collects all the output
If there were an easy way to "promote" shell variables up out of their subshell namespaces without needing `` or $() or read, subshells would be a heck of a lot more useful...
-Adam
-----Original Message----- From: Trevor Cordes trevor@tecnopolis.ca Sent: Sunday, February 26, 2023 8:23 PM To: Adam Thompson athompso@athompso.net Cc: Continuation of Round Table discussion roundtable@muug.ca Subject: Re: [RndTbl] shell quoting inside $( )?
I can't see any way to "bifurcate output" in bash without using tee. I'm pretty sure zsh (and probably fish) can do it, but not sure of the syntax there either:
$ eval $((openssl x509 -noout -text -in /etc/pki/tls/certs/tecnopolis.ca.crt | tee >(echo -n EXPD="'"`date -d"$(sed -n 's/^.*Not After : //p')" +%Y%b%d`"'" ) >(echo SUBJ="'"`sed -n 's/^.*Subject: .*CN = //p'`"'" ) 1>&2 ) 2>/dev/null)
$ echo $SUBJ tecnopolis.ca $ echo $EXPD 2024Feb22
If you can figure out a way to get the vars out of the subshell and into 2 different vars without using the eval $() then you're probably better off. Even though I protect the tainted inputs with '', someone could possibly plant a ' in the SUBJ and thus this is a sec hole.
Of course you could eliminate the eval and assign to a var and then run a 2nd command to split them into SUBJ and EXPD, but I was going for a oneliner. (Get rid of the "'" adders then.)
I'm going to toy with the idea of using {} subshells which might allow elimination of the eval.
Of course this would be much cleaner in a perl oneliner... and if you're already bringing in sed, how much worse is perl?
On 2023-02-27 Adam Thompson wrote:
Found my answer, sort of. Either use mkfiko and tee, or use bash/zsh/ksh88 process substitution a la "cmd >(subcmd1) >(subcmd2)",
That's what I said half an hour ago! Doh
I managed to improve mine to eliminate the eval:
$ read SUBJ EXPD <<<$(echo $((openssl x509 -noout -text -in /etc/pki/tls/certs/tecnopolis.ca.crt |tee >(date -d"$(sed -n 's/^.*Not After : //p')" +%Y%b%d) >(sed -n 's/^.*Subject: .*CN = //p') 1>&2 ) 2>/dev/null))
$ echo "subj $SUBJ expd $EXPD" subj tecnopolis.ca expd 2024Feb22
but I don't see any good way of getting the output from subcmd1/2 into variables as they run in subshells. It would be do-able by piping the whole thing into a "while read X" loop, but that's arguably getting into "the cure is worse than the disease" territory.
Yes, the subshell/command problem is the difficult factor here. My updated example above also uses read, but without a loop. You need the echo and multi-subshells to get the 2 outputs onto 1 line.
Here's the funny part: the >() constructs start async ps's and you don't know whose output will come first! Yet no matter what I did the SUBJ always comes out first. I even put sleeps in the >() constructs to try to influence who outputs first, but it didn't matter! I wonder why the output order is the way it is (backwards) and always constant...
Untested as yet, but should work:
( openssl x509 -noout -text -in "$1" >(sed -n 's/^.*Not After : /A /p' | xargs date +%Y%b%d -d) >(sed -n 's/^.*Subject: .*CN = /B /p') ) | while read X; do case $X in A) EXPD="$X" ;; B) SUBJ="$X" ;; esac ;
<do something with EXPD and SUBJ>
Pretty sure you are *forced* to use tee or something like it. You can't just use >() with openssl. >() replaces itself with /dev/fd/X and sets up an async to read from it, which means nothing to openssl. You need tee to do the writing to that fd/X. Ran into that grief when I was working on it.
Nice: you are getting around the order issue with A / B, which is smart, but then kind of forces you into the loop. Still one line though. And the loop is no more evil than my read <<< echo hack.
If there were an easy way to "promote" shell variables up out of their subshell namespaces without needing `` or $() or read, subshells would be a heck of a lot more useful...
I was trying really hard to use my own fd's like 3 & 4 (not the ones used by >()) to get the output out of each >() construct and be able to differentiate them. But it wouldn't work... maybe because the >() is async, and I need to write to each fd in the construct and then read them out of the construct. Dunno. Use of fds 3 & 4 hurts my brain. Ideally the EXPD >() could write out to fd3 and SUBJ to fd4 and an outer shell could then read them. Or maybe that's impossible.
Rethinking the problem a bit, I came up with the following, which may or may not be an improvement over what others have already suggested...
T=$(openssl x509 -noout -text -in "$1" | sed -n 's/^.*Not After : /EXPD=/p;s/^.*Subject: .*CN *= */SUBJ=/p' | sed "s/=(.*)/='\1'/") eval $T EXPD=$(date -d"$EXPD" +%Y%b%d)
The first command does most of the leg-work, stripping out the crud and leaving us with 2 mostly usable variable definitions. (I've added "*"'s after the spaces surrounding the "CN = " string, since those spaces may not always be there, and were missing for my test case.)
The second command sets the two variables (and should be mostly safe, considering the constraints on those fields), and the third one then massages the EXPD variable into the desired date format.
Hope this helps!
Gilbert
On 2023-02-26 6:07 p.m., Adam Thompson wrote:
I’m trying to figure out if there’s a way to do this more compactly (for reasons…):
T=$(openssl x509 -noout -text -in "$1")
EXPD=$(date -d"$(echo "$T" | sed -n 's/^.*Not After : //p')" +%Y%b%d)
SUBJ=$(echo "$T" | sed -n 's/^.*Subject: .*CN = //p')
I am having two issues:
- Bash doesn’t happily let me embed double quotes inside a subshell inside a subshell inside double-quotes. All of the double-quotes are required, AFAICT. So doing X=$(…”$(…”xxx”)”) just doesn’t work because bash doesn’t parse nested double-quotes. But both the inner var and the result can and will have spaces in them.
- I can’t remember how in shell (bash, in this case) how to bifurcate stdout and have it run to two pipelines. I figure echo is a builtin, so should at least be lighter than re-running the monstrosity that is openssl over and over. I have a vague recollection that this is harder than it sounds, and may not be worth it…
Any suggestions on how to solve either issue? I’m sure I’ve just forgotten some obvious technique, hopefully someone can jog my mind.
-Adam
On 2023-02-27 Gilbert Detillieux wrote:
Rethinking the problem a bit, I came up with the following, which may or may not be an improvement over what others have already suggested...
T=$(openssl x509 -noout -text -in "$1" | sed -n 's/^.*Not After : /EXPD=/p;s/^.*Subject: .*CN *= */SUBJ=/p' | sed "s/=(.*)/='\1'/") eval $T EXPD=$(date -d"$EXPD" +%Y%b%d)
Points lost for not-a-one-liner ;-)
But you were smart to eliminate the multi-stream (tee >()) ideas, and thus the async-order issue. And save some forks.
NOW, the near-ideal way is to do what you did but see if sed has a regex PCRE /e style exec option which you could then use to run the date from within the regex itself. Quoting might get hairy in that case, but it would allow it to go back to just 1 line!
Perl could do it (with the eval, or pipe into read) and I might give that a try for fun.
(In any case, you can save a line by putting the eval around line 1, no need for $T.)
Does no one know how to get FD 3 & 4 used inside the >()'s and thus be able to pass it (I think) out of the <()'s and capture again in the root-shell context with a read thus eliminating the eval?
Is this a pipe dream? something like (pseudocode):
(openssl | tee >(sed foo|date >3) >(sed subj >4) ) | read -u3 expd | read -u4 subj
???
Perl version. Cleaner? No eval. No >(). One line. Relies on read to fill the bash vars. Uses Gilbert's just-one-filter-pass idea. Does the date transform at the very end in perl: would be a sec hole if $e is injected with bad things. Could easily fix with setting the $e regex from . to [-.a-zA-Z0-9]. Could also die in the END if !$e.
$ read SUBJ EXPD <<<$(openssl x509 -noout -text -in /etc/pki/tls/certs/tecnopolis.ca.crt | perl -ne '($e)=/^.*Not After : (.*)/ if !$e; ($s)=/^.*Subject: .*CN = (.*)/ if !$s; END { print $s." ".`date -d"$e" +%Y%b%d`}') $ echo s=$SUBJ e=$EXPD s=tecnopolis.ca e=2024Feb22
I like the perl approach because it has the least # of forks, and really the sky is the limit for taint cleaning and sanity checks. Plus I find it more readable than bash, and perl is highly optimized for PCRE so should be pretty fast. I also understand perl's quoting intimately vs my general haze with bash.
Wow, I think I unwittingly invoked Cunningham's Law with my initial post...
Thank you to everyone for the hints, tips, and alternate approaches - I've learned a few new things through this!
-Adam
Get Outlook for Androidhttps://aka.ms/AAb9ysg ________________________________ From: Trevor Cordes trevor@tecnopolis.ca Sent: Tuesday, February 28, 2023 12:42:55 AM To: Gilbert Detillieux Gilbert.Detillieux@umanitoba.ca Cc: Continuation of Round Table discussion roundtable@muug.ca; Adam Thompson athompso@athompso.net Subject: Re: [RndTbl] shell quoting inside $( )?
Perl version. Cleaner? No eval. No >(). One line. Relies on read to fill the bash vars. Uses Gilbert's just-one-filter-pass idea. Does the date transform at the very end in perl: would be a sec hole if $e is injected with bad things. Could easily fix with setting the $e regex from . to [-.a-zA-Z0-9]. Could also die in the END if !$e.
$ read SUBJ EXPD <<<$(openssl x509 -noout -text -in /etc/pki/tls/certs/tecnopolis.ca.crt | perl -ne '($e)=/^.*Not After : (.*)/ if !$e; ($s)=/^.*Subject: .*CN = (.*)/ if !$s; END { print $s." ".`date -d"$e" +%Y%b%d`}') $ echo s=$SUBJ e=$EXPD s=tecnopolis.ca e=2024Feb22
I like the perl approach because it has the least # of forks, and really the sky is the limit for taint cleaning and sanity checks. Plus I find it more readable than bash, and perl is highly optimized for PCRE so should be pretty fast. I also understand perl's quoting intimately vs my general haze with bash.