Compare command substitution string not null in fish - fish

I'm trying to test if a git repository has the showuntrackedfiles option set to no.
My first aproch was:
if test (git config --get status.showuntrackedfiles) = no
echo "hi"
else
echo "bye"
end
but this breaks if showuntrackedfiles is not set.
test: Missing argument at index 2
(Type 'help test' for related documentation)
bye
The only workaround I've found is:
if test (git config --get status.showuntrackedfiles; or echo "") = no
echo "hi"
else
echo "bye"
end
but it seems hacky. Is there a better way of approaching this?

No less hacky, but here's a way to see if a command substitution returned nothing:
set output (git config ...)
if test (count $output) -gt 0 -a $output = no; ...
Recall fish variables are lists, so count is checking if the list has more then zero elements.

Related

csh: set: No match

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.

How to define a function that either takes arguments or doesnt?

I am using Fish shell....
Basically, to do something like this:
if (first argument == --r) {
do something
} else {
Do something
if (first argument == --n) {
do more
}
}
To achieve the first if statement I tried:
if test (count $argv) -eq 1 -a $argv[1] = '--r'
But that gives a message:
test: Missing argument at index 6
Functions in Fish don't require their parameters to be specified when you define the function. Any arguments sent to the function by the user are automatically stored in an array called argv. In order to determine whether arguments were sent, you can either count the number of elements in the array, or determine the length of the array as a string. I do the latter:
function my_func
if [ -z "$argv" ]; # No arguments
echo "No arguments supplied"
return
else # At least one argument
if [ "$argv[1]" = "--r" ];
echo "Excellent!"
return
end
end
end
If you prefer to use count, then it will look more like this:
function my_func
if [ (count $argv) -eq 1 -a "$argv[1]" = "--r" ];
# Exactly one argument with specified value "--r"
echo "Excellent!"
return
else # May have arguments, but none equal to "--r"
echo "Give me the right arguments"
return
end
end
Your use of set -q argv[1] is also a good option. But when you're checking for string equality, don't forget to surround your variable in quotes, like this: test "$argv[1]" = "--r".
Here's another method, using the switch...case conditional test:
function my_func
# No arguments
if [ -z "$argv" ]; and return
# At least one argument
switch $argv[1];
case --r;
# do some stuff
return
case "*";
# Any other arguments passed
return
end
end
end
This worked for me:
if set -q argv[1] ;and test $argv[1] = "--r"
Let's start with the error you get when executing this:
if test (count $argv) -eq 1 -a $argv[1] = '--r'
That happens because fish first expands $argv[1] then executes test. If argv has no values then that statement turns into
if test 0 -eq 1 -a = '--r'
Which isn't valid syntax for the test command. It doesn't matter that the first sub-expression evaluates to false since test parses the entire expression before evaluating it.
Rather than doing test (count $argv) -eq 1 just do set -q argv[1] to test if argv has at least one argument. Note the lack of a dollar-sign.
If you're using fish 2.7.0 or newer I recommend the new argparse builtin for handling arguments. Several of the standard functions that ship with fish use it so you can look at them, as well as man argparse, for examples of how to use it. Using argparse is almost always safer, less likely to result in bugs due to sloppy argument parsing using hand written fish script, and will provide argument parsing semantics identical to most commands including all the fish builtins. Including correctly handling short and long flags.
well if the argument is optional then you can do this by:
//check if variable exists
if (typeof variable === 'undefined'){
}
else{
if(typeof variable){}
}

How do I use argv with a wildcard?

I'm trying to write a function which will change the extension for all matching files in the current directory:
function chext
if count $args -eq 2 > /dev/null
for f in (ls *$argv[1])
mv (basename $f $argv[2]) (basename $f $argv[1])
end
else
echo "Error: use the following syntax"
echo "chext oldext newext"
end
end
I keep getting this output though:
(*master) λ chext markdown txt
No matches for wildcard '*$argv[1]'. (Tip: empty matches are allowed in 'set', 'count', 'for'.)
~/.config/fish/config.fish (line 1): ls *$argv[1]
^
in command substitution
called on line 60 of file ~/.config/fish/config.fish
in function 'chext'
called on standard input
with parameter list 'markdown txt'
I know there must be a way to interpolate the variable and keep * as a wildcard, but I can't figure it out. I have tried using (string join '*' $argv[1]), but it turned the wildcard into a string.
I did get this to almost work:
function chext
if count $args -eq 2 > /dev/null
for f in (ls (string join * $argv[1]))
mv (basename $f $argv[2]) (basename $f $argv[1])
end
else
echo "Error: use the following syntax"
echo "chext oldext newext"
end
end
It removed the extensions from all of my files but didn't add the new one, then gave me an error which was all of the file names combined and said file name too long.
UPDATE:
I'm sure there is a way to get the correct ls working, but I did get this working successfully:
if count $args -eq 2 > /dev/null
for f in (ls)
mv $f (string replace -r \.$argv[1]\$ .$argv[2] (basename $f))
end
else
echo "Error: use the following syntax"
echo "chext oldext newext"
end
end
The answer is in the message:
No matches for wildcard '*$argv[1]'. (Tip: empty matches are allowed in 'set', 'count', 'for'.)
That means
for i in *argv[1]
works, as does
set -l expanded *argv[1]
If you try to launch any other command with a glob that doesn't match anything, fish will skip the execution and print an error.
Your function has a number of other issues, allow me to go through them one by one.
if count $args -eq 2 > /dev/null
count does not take the "-eq 2" argument. It will return 0 (i.e. true) whenever it is given an argument, so this will always be true.
What you tried to do was if test (count $args) -eq 2.
What also works (and what I happen to like more) is if set -q argv[2]
(also, it's "argv", not "args").
for f in (ls *$argv[1])
Please do not use the output of ls. It doesn't have as much problems in fish as it does in bash (where it'll break once a file has a space, tab or newline in the name), but it's unnecessary (it launches an additional process) and still has an issue - it'll break when a file has a newline in its name.
Just do for f in *$argv[1], which will also nicely solve your question.
mv (basename $f $argv[2]) (basename $f $argv[1])
I'm not sure what basename you are using here, but mine just removes a suffix. So if you did chext .opus .ogg, this would execute
mv (basename song.opus .ogg) (basename song.opus .opus)
which would resolve to
mv song.opus song
Personally, I'd use string replace for this. Something like
mv $f (string replace -- $argv[1] $argv[2] $f)
(Note: This would do the wrong thing if the extension string appears before, e.g. if you had a file called "f.ogg-banana.ogg" only the first ".ogg" would be changed. You might want to use regular expressions for this: string replace -r -- "$argv[1]\$" $argv[2] $f)

comparison the content of a text and csv files

I need a sample bash script to compare a first line of a file(Result.txt) to first row and column of another file(table.csv), then send the result to an html file.
I am very basic in coding, this is what I found so far:
#!/bin/sh
Result.txt="$(head -n 1 < $1|tail -n 1)"
table.csv="$(head -n 1 < $2|tail -n 1)"
test "$R.txt" = "$sheet.csv" && (echo The same; exit 0)
Appreciate your help
Slightly tweaking your script.
#!/bin/bash
Res=$(head -n 1 "$1")
tab=$(head -n 1 "$2")
[[ $Res == $tab ]] && echo The same
Notes
"dot" is not a valid identifier (i.e. variable name) character: valid is letters, numbers and underscore, and the first character cannot be a number.
if you're doing head -1, there's no need to pipe that into tail -1
I think [[ is more readable than test, primarily because [[ forces you to have ]]
parentheses launch a subshell which is overkill for an echo statement.
the exit will only exit the subshell not your program
if you have multiple statements, use if ...; then ...; fi -- it's more readable.

tcl script to compare 2 texts and return the line numbers of the lines that differ only. Also, How to avoid "child process exit abnormally"?

can anybody guide me how to write tcl script to compare 2 texts and return the line numbers of the lines that differ only ?
I know how to do it in bash, but to include the bash in tcl doesnt seem to be very neat, here's the bash command :
diff --old-line-format '%L' --new-line-format '' --unchanged-line-format '' <(nl File1) <(nl File2) | awk '{print $1 }' > difflines
To include this in tcl, i did the following :
exec cat nl File1 > File11
exec cat nl File2 > File22
exec diff --old-line-format {%L} --new-line-format {} --unchanged-line-format {} <
File11 < File22 | awk {{print $1 }} > difflines
Is there a cleaner way ?
Also if there's difference i get the "child exit abnormally", how can i avoid this ?
Thanks
The struct::list package in Tcllib has tools for computing longest-common-subsequences, which is the key part of a diff tool. To use, you load your data into Tcl and split it into a list of lines:
proc getLines {filename} {
set f [open $filename]
set result [split [read $f] "\n"]
close $f
return $result
}
Then you can get the information about the common elements (== common lines):
set sharedLineInfo [struct::list longestCommonSubsequence $file1_lines $file2_lines]
This returns a pair of lists, where each list is the indices (counting from zero) of the common lines; the first list will be for the first file, and the second list for the second file. Any line number not listed will be one that has changed.
There's also a function to invert the information provided to get instructions on how to change one sequence into the other:
set changes [struct::list lcsInvert $sharedLineInfo \
[llength $file1_lines] [llength $file2_lines]]
This returns a list of triples, where the first is the operation performed (added, changed or deleted) and the second and third are the ranges of indices in each of the relevant lists (i.e., zero-based line numbers).
I'm not quite sure how to take this information and produce what you are looking for, but I guess we could put it together like this:
package require struct::list
proc getLines {filename} {
set f [open $filename]
set result [split [read $f] "\n"]
close $f
return $result
}
proc variedLines {filename1 filename2} {
set l1 [getLines $filename1]
set l2 [getLines $filename2]
lassign [struct::list longestCommonSubsequence $l1 $l2] common1
set result {}
for {set i 0} {$i < [llength $l1]} {incr i} {
if {$i ni $common1} {
lappend result [expr {$i + 1}]
}
}
return $result
}
If you want the results written to a file, puts $f [join $someList "\n"] is likely to feature, but I'll leave that as an exercise…
Regarding "child process exited abnormally", from the exec man page (emphasis mine):
If any of the commands in the pipeline exit abnormally or are killed or suspended, then exec will return an error and the error message will include the pipeline's output followed by error messages describing the abnormal terminations; the -errorcode return option will contain additional information about the last abnormal termination encountered. If any of the commands writes to its standard error file and that standard error is not redirected and -ignorestderr is not specified, then exec will return an error; the error message will include the pipeline's standard output, followed by messages about abnormal terminations (if any), followed by the standard error output.
"commands exit abnormally" means that the command exits with a non-zero status. Some common commands like grep and diff return a non-zero exit status to indicate something normal, so you have to wrap that exec call in a catch
set rc [catch {exec bash -c {
diff --old-line-format '%L' --new-line-format '' --unchanged-line-format '' <(nl File1) <(nl File2) | awk '{print $1}' > difflines
}} output]
if {$rc == 0} {
puts "no differences found"
} elseif {$rc == 1} {
puts "differences found:"
puts $output
} else {
puts "diff returned an error: $output"
}