Set variable value inside switch statement Tcl - switch-statement

Attempting to set two variables values according to the user input in a third variable. I am trying to use a switch statement but I am not sure if it is actually possible.
switch $sample_type_input {
CORE { set sample_type 1 \
set cutting ""}
SWC { set sample_type 2 \
set cutting ""}
CUTT { set sample_type 3 \
set cutting "CUTT" }
}
I keep getting a 'wrong # args: should be "set varName ?newBalue?"` error.

The backslash at the end of the line acts as a line continuation, concatenating this line and the next line into a single logical line of code. This is necessary in a case where one has to break up a command invocation into two or more lines, because of the parsing rules:
set foo
bar
is interpreted as two command invocations: set foo and bar.
set foo \
bar
is a single command invocation: set foo bar.
Note that the backslash must be the very last character before the newline: e.g. a space between the backslash and the newline simply makes the invocation end with a regular space and a quoted space:
# there is a space after the backslash here
set foo \
puts "{$foo}"
# => { }
If your command invocation includes an argument in braces, it's typically quite all right to break it up into several lines without a backslash, since all whitespace including newlines is quoted by the braces anyway:
set foo {
bar
baz
}
And indeed if the argument within braces is a script containing more than one command invocation, you need to break it up into lines (or insert semicolons), since newlines are significant as command line separators. Therefore this fails:
if {true} {set foo bar \
set baz quz}
as it concatenates two command invocations into the single code line set foo bar set baz quz, providing the set command with five arguments when it expects one or two. The correct way is to write it as
if {true} {set foo bar ; set baz quz}
or
if {true} {set foo bar
set baz quz}
letting the semicolon or newline separate the command invocations in the script argument. Stylistic convention is to add additional newlines at the beginning and end of a multi-line script argument, like this:
if {true} {
set foo bar
set baz quz
}
This is similar to C brace styles, but not quite the same thing. One common beginner mistake is to try to use some other C bracing convention such as
if {true}
{
set foo bar
set baz quz
}
which won't work since it introduces a codeline-separating newline between the first and second arguments to if. If you can't live without this bracing convention, you need to add a line continuation:
if {true} \
{
set foo bar
set baz quz
}
Documentation: if, puts, set, Summary of Tcl language syntax
(Note: the 'Hoodiecrow' mentioned in the comments is me, I used that nick earlier.)

The answer was found somewhere else in my script. #Hoodiecrow was correct about the \, they needed to be removed but unfortunately for me I was getting the same error from the other part of my code.

Related

Using Pest.rs how can I manage a multi-line syntax where a line ends in "\"?

A common idiom for bash is is to use \ to escape the newline at the end of the line,
If a \<newline> pair appears, and the backslash is not itself quoted, the \<newline> is treated as a line continuation (that is, it is removed from the input stream and effectively ignored).
Such that
FOO \
BAR
is the same as,
FOO BAR
How would I write this grammar into pest.rs? Note this means that NEWLINE is significant in my grammar, and I can't merely ignore it.
One method is to set your
WHITESPACE = { ( " "* ~ "\\" ~ NEWLINE ~ " "* ) }
This keeps regular newlines significant unless they're prefixed by \.

What does `${1+/$1}` mean in Bash?

I was reading a Bash script with this line:
FOO=${1+/$1}
What does this line do?
The / makes it look slightly more confusing than it is, but this is just an example of ${foo+bar}, which expands to bar if $foo is set.
In this case, the variable is $1, the first positional parameter passed to the script or function.
example () {
echo "${1+/$1}"
}
example # outputs nothing
example '' # outputs "/"
example foo # outputs "/foo"
There is a table that summarises these parameter expansions in the spec. The rules for ${parameter+word} are:
Set and Not Null: substitute word
Set But Null: substitute word
Unset: substitute null
So to answer your question directly, FOO=${1+/$1} assigns /$1 to FOO is $1 is set, otherwise FOO is set to null (an empty string).

Vim: How to split command line arguments?

According to the f-args documentation, the command line arguments passed to a user defined function, will be automatically split at white-space or tabs as the help shows:
*<f-args>*
To allow commands to pass their arguments on to a user-defined function, there
is a special form <f-args> ("function args"). This splits the command
arguments at spaces and tabs, quotes each argument individually, and the
<f-args> sequence is replaced by the comma-separated list of quoted arguments.
See the Mycmd example below. If no arguments are given <f-args> is removed.
To embed whitespace into an argument of <f-args>, prepend a backslash.
<f-args> replaces every pair of backslashes (\\) with one backslash. A
backslash followed by a character other than white space or a backslash
remains unmodified. Overview:
command <f-args> ~
XX ab 'ab'
XX a\b 'a\b'
XX a\ b 'a b'
XX a\ b 'a ', 'b'
XX a\\b 'a\b'
....
However the most basic example does not work:
function! TestFunc(...)
echo a:0
echo a:000
endfunction
command! -nargs=? TestFunc call TestFunc(<f-args>)
-------------------------------------------------
> :TestFunc foo bar bla bla, fooo
> 1
> ['foo bar bla bla, fooo']
> :TestFunc foo\ bar
> 1
> ['foo\ bar']
I have a bunch of arguments split by whitespaces but vim sees it as one. Why does that happen?
Side question ( can it be somehow configured to actually split arguments at comma? )
You specified -nargs=?.
The documentation says:
-nargs=? 0 or 1 arguments are allowed
-nargs=1 Exactly one argument is required, it includes spaces
and
Arguments are considered to be separated by (unescaped) spaces or tabs in this
context, except when there is one argument, then the white space is part of
the argument.
(Emphasis mine.)
If you use -nargs=* instead, you get the normal behavior.

Multiple :g and :v commands in one statement

I have this file
foo
foo bar
foo bar baz
bar baz
foo baz
baz bar
bar
baz
foo 42
foo bar 42 baz
baz 42
I want to
Select lines which contain foo and do NOT contain bar
Delete lines which contain foo and do NOT contain bar
I read somewhere (can't find the link) that I have to use :exec with | for this.
I tried the following, but it doesn't work
:exec "g/foo" # works
:exec "g/foo" | exec "g/bar" -- first returns lines with foo, then with bar
:exec "g/foo" | :g/bar -- same as above
And ofcourse if I cannot select a line, I cannot execute normal dd on it.
Any ideas?
Edit
Note for the bounty:
I'm looking for a solution that uses proper :g and :v commands, and does not use regex hacks, as the conditions may not be the same (I can have 2 includes, 3 excludes).
Also note that the last 2 examples of things that don't work, they do work for just deleting the lines, but they return incorrect information when I run them without deleting (ie, viewing the selected lines) and they behave as mentioned above.
I'm no vim wizard, but if all you want to do is "Delete lines which contain foo and do NOT contain bar" then this should do (I tried on your example file):
:v /bar/s/.*foo.*//
EDIT: actually this leaves empty lines behind. You probably want to add an optional newline to that second search pattern.
This might still be hackish to you, but you can write some vimscript to make a function and specialized command for this. For example:
command! -nargs=* -range=% G <line1>,<line2>call MultiG(<f-args>)
fun! MultiG(...) range
let pattern = ""
let command = ""
for i in a:000
if i[0] == "-"
let pattern .= "\\(.*\\<".strpart(i,1)."\\>\\)\\#!"
elseif i[0] == "+"
let pattern .= "\\(.*\\<".strpart(i,1)."\\>\\)\\#="
else
let command = i
endif
endfor
exe a:firstline.",".a:lastline."g/".pattern."/".command
endfun
This creates a command that allows you to automate the "regex hack". This way you could do
:G +foo -bar
to get all lines with foo and not bar. If an argument doesn't start with + or - then it is considered the command to add on to the end of the :g command. So you could also do
:G d +foo -bar
to delete the lines, or even
:G norm\ foXp +two\ foos -bar
if you escape your spaces. It also takes a range like :1,3G +etc, and you can use regex in the search terms but you must escape your spaces. Hope this helps.
This is where regular expressions get a bit cumbersome. You need to use the zero width match \(search_string\)\#=. If you want to match a list of items in any order, the search_string should start with .* (so the match starts from the start of the line each time). To match a non-occurrence, use \#! instead.
I think these commands should do what you want (for clarity I am using # as the delimiter, rather than the usual /):
Select lines which contain foo and bar:
:g#\(.*foo\)\#=\(.*bar\)\#=
Select lines which contain foo, bar and baz
:g#\(.*foo\)\#=\(.*bar\)\#=\(.*baz\)\#=
Select lines which contain foo and do NOT contain bar
:g#\(.*foo\)\#=\(.*bar\)\#!
Delete lines which contain foo and bar
:g#\(.*foo\)\#=\(.*bar\)\#=#d
Delete lines which contain foo and do NOT contain bar
:g#\(.*foo\)\#=\(.*bar\)\#!#d
You won't achieve your requirements unless you're willing to use some regular expressions since the expressions are what drives :global and it's opposite :vglobal.
This is no hacking around but how the commands are supposed to work: they need an expression to work with. If you're not willing to use regular expressions, I'm afraid you won't be able to achieve it.
Answer terminates here if you're not willing to use any regular expressions.
Assuming that we are nice guys with an open mind, we need a regular expression that is true when a line contains foo and not bar.
Suggestion number 5 of Prince Goulash is quite there but doesn't work if foo occurs after bar.
This expression does the job (i.e. print all the lines):
:g/^\(.*\<bar\>\)\#!\(.*\<foo\>\)\#=/
If you want to delete them, add the delete command:
:g/^\(.*\<bar\>\)\#!\(.*\<foo\>\)\#=/d
Description:
^ starting from the beginning of the line
\(.*\<bar\>\) the word bar
\#! must never appear
\(.*\<foo\>\)\#= but the word foo has to appear anywhere on the line
The two patterns could also be swapped:
:g/^\(.*\<foo\>\)\#=\(.*\<bar\>\)\#!/
yields the same results.
Tested with the following input:
01 foo
02 foo bar
03 foo bar baz
04 bar baz
05 foo baz
06 baz bar
07 bar
08 baz
09 foo 42
10 foo bar 42 baz
11 42 foo baz
12 42 foo bar
13 42 bar foo
14 baz 42
15 baz foo
16 bar foo
Regarding multiple includes/excludes:
Each exclude is made of the pattern
\(.*\<what_to_exclude\>\)\#!
Each include is made of the pattern
\(.*\<what_to_include\>\)\#=
To print all the lines that contain foo but not bar nor baz:
g/^\(.*\<bar\>\)\#!\(.*\<baz\>\)\#!\(.*\<foo\>\)\#=/
Print all lines that contain foo and 42 but neither bar nor baz:
g/^\(.*\<bar\>\)\#!\(.*\<baz\>\)\#!\(.*\<foo\>\)\#=\(.*\<42\>\)\#=/
The sequence of the includes and excludes is not important, you could even mix them:
g/^\(.*\<bar\>\)\#!\(.*\<42\>\)\#=\(.*\<baz\>\)\#!\(.*\<foo\>\)\#=/
One might think a combination like :g/foo/v/bar/d would work, but unfortunately this isn't possible, and you will have to recur to one of the proposed work-arounds.
As described in the help, behind the scenes the :global command works in two stages,
first marking the lines on which to operate,
then performing the operation on them.
Out of interest, I had a look at the relevant parts in the Vim source: In ex_cmds.c, ex_global(), you will find that the global flag global_busy prevents repeated execution of the command while it is busy.
You want to employ a negative look ahead. This article gives more or less the specific example you are trying to achieve.
http://www.littletechtips.com/2009/09/vim-negative-match-using-negative-look.html
I changed it to
:g/foo(.*bar)\#!/d
Please let us know if you consider this a regex hack.
I will throw my hat in the ring. As vim's documentation explicitly states recursive global commands are invalid and the regex solution will get pretty hairy quickly, I think this is job for a custom function and command. I have created the :G command.
The usage is as :G followed by patterns surrounded by /. Any pattern that should not match is prefixed with a !.
:G /foo/ !/bar/ d
This will delete all lines that match /foo/ and does not match /bar/
:G /42 baz/ !/bar/ norm A$
This will append a $ to all lines matching /42 baz/ and that don't match /bar/
:G /foo/ !/bar/ !/baz/ d
This will delete all lines that match /foo/ and does not match /bar/ and does not match /baz/
The script for the :G command is below:
function! s:ManyGlobal(args) range
let lnums = {}
let patterns = []
let cmd = ''
let threshold = 0
let regex = '\m^\s*\(!\|v\)\=/.\{-}\%(\\\)\#<!/\s\+'
let args = a:args
while args =~ regex
let pat = matchstr(args, regex)
let pat = substitute(pat, '\m^\s*\ze/', '', '')
call add(patterns, pat)
let args = substitute(args, regex, '', '')
endwhile
if args =~ '\s*'
let cmd = 'nu'
else
let cmd = args
endif
for p in patterns
if p =~ '^(!\|v)'
let op = '-'
else
let op = '+'
let threshold += 1
endif
let marker = "let l:lnums[line('.')] = get(l:lnums, line('.'), 0)" . op . "1"
exe a:firstline . "," . a:lastline . "g" . substitute(p, '^(!\|v)', '', '') . marker
endfor
let patterns = []
for k in keys(lnums)
if threshold == lnums[k]
call add(patterns, '\%' . k . 'l')
endif
endfor
exe a:firstline . "," . a:lastline . "g/\m" . join(patterns, '\|') . "/ " . cmd
endfunction
command! -nargs=+ -range=% G <line1>,<line2>call <SID>ManyGlobal(<q-args>)
The function basically parses out the arguments then goes and marks all matching lines with each given pattern separately. Then executes the given command on each line that is marked the proper amount of times.
All right, here's one which actually simulates recursive use of global commands. It allows you to combine any number of :g commands, at least theoretically. But I warn you, it isn't pretty!
Solution to the original problem
I use the Unix program nl (bear with me!) to insert line numbers, but you can also use pure Vim for this.
:%!nl -b a
:exec 'norm! qaq'|exec '.,$g/foo/d A'|exec 'norm! G"apddqaq'|exec '.,$v/bar/d'|%sort|%s/\v^\s*\d+\s*
Done! Let's see the explanation and general solution.
General solution
This is the approach I have chosen:
Introduce explicit line numbering
Use the end of the file as a scratch space and operate on it repeatedly
Sort the file, remove the line numbering
Using the end of the file as a scratch space (:g/foo/m$ and similar) is a pretty well-known trick (you can find it mentioned in the famous answer number one). Also note that :g preserves relative ordering of the lines – this is crucial. Here we go:
Preparation: Number lines, clear "accumulator" register a.
:%!nl
qaq
The iterative bit:
:execute global command, collect matching lines by appending them into the accumulator register with :d A.
paste the collected lines at the end of the file
repeat for range .,$ (the scratch space, or in our case, the "match" space)
Here's an extended example: delete lines which do contain 'foo', do not contain 'bar', do contain '42' (just for the demonstration).
:exec '.,$g/foo/d A' | exec 'norm! G"apddqaq' | exec '.,$v/bar/d A' | exec 'norm! G"apddqaq' | exec '.,$g/42/d A' | exec 'norm! G"apddqaq'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(this is the repeating bit)
When the iterative bit ends, the lines .,$ contain the matches for your convenience. You can delete them (dVG) or whatever.
Cleanup: Sort, remove line numbers.
:%sort
:%s/\v^\s*\d+\s*
I'm sure other people can improve on the details of the solution, but if you absolutely need to combine multiple :gs and :vs into one, this seems to be the most promising solution.
The in-built solutions looks very complex.
One easy way would be to use LogiPat plugin:
Doc: http://www.drchip.org/astronaut/vim/doc/LogiPat.txt.html
Plugin: http://www.drchip.org/astronaut/vim/index.html#LOGIPAT
With this, you can easily search for patterns.
For e.g, to search for lines containing foo, and not bar, use:
:LogiPat "foo"&!("bar")
This would highlight all the lines matching the logical pattern (if you have set hls).
That way you can cross-check whether you got the correct lines, and then traverse with 'n', and delete with 'dd', if you wish.
I realize you explicitly stated that you want solutions using :g and :v, but I firmly believe this is a perfect example of a case where you really should use an external tool.
:%!awk '\!/foo/ || /bar/'
There's no need to re-invent the wheel.
Select lines which contain foo and do NOT contain bar
Delete lines which contain foo and do NOT contain bar
This can be done by combining global and substitute commands:
:v/bar/s/.*foo.*//g

How to replace text between quotes in vi

Say I have this line of code:
$query = "SELECT * FROM table";
Is there a command in vi/vim which can instantly delete everything between quotes and position the cursor between them so I can start typing?
Use ci", which means: change what inside the double quotes.
You can also manipulate other text objects in a similar way, e.g.:
ci' - change inside the single quotes
ciw - change inside a word
ci( - change inside parentheses
dit - delete inside an HTML tag, etc.
More about different vim text objects here.
You can select between quotes and then delete (d), change (c) etc. using
vi"
Similarly, you can substitute braces, brackets, XML elements etc. thus:
vi(
vi{
vit
or to simply change/delete, do the corresponding di", ci" etc. Substituting a for i will encompassing the surrounding elements (so you mark or change the brackets and contents, for example)
I've made a plugin vim-textobj-quotes: https://github.com/beloglazov/vim-textobj-quotes
It provides text objects for the closest pairs of quotes of any type and supports quotes spanning multiple lines. Using only iq or aq it allows you to operate on the content of single ('), double ("), or back (`) quotes that currently surround the cursor, are in front of the cursor, or behind (in that order of preference). In other words, it jumps forward or backwards when needed to reach the quotes.
It's easier to understand by looking at examples (the cursor is shown with |):
Before: foo '1, |2, 3' bar; after pressing diq: foo '|' bar
Before: foo| '1, 2, 3' bar; after pressing diq: foo '|' bar
Before: foo '1, 2, 3' |bar; after pressing diq: foo '|' bar
Before: foo '1, |2, 3' bar; after pressing daq: foo | bar
Before: foo| '1, 2, 3' bar; after pressing daq: foo | bar
Before: foo '1, 2, 3' |bar; after pressing daq: foo | bar
The examples above are given for single quotes, the plugin works exactly the same way for double (") and back (`) quotes.
You can also use any other operators: ciq, diq, yiq, viq, etc.
Please have a look at the github page linked above for more details.
An addition to Brian's answer, you can also p(paste) and y(yank) the new value, so if you want to replace the value inside quotes with another value, you could do yi" on the selection that you want to copy, vi" to select the area that you want to replace and then just p to properly replace the value.
From already inside the quotes you can do
di"
Read it as delete inside "
The chosen answer is suitable ONLY for ViM but NOT for vi. The question is inaccurate as well because the author did not mention what is initial position of the cursor. If we assume that the cursor is inside the double quotes then for vi the answer will be:
T"ct"
Where:
T" - move back just after the " character
c - change command
t" - provide end position for c command, where it should stop erasing characters, in other words the range to change

Resources