I am using ed a unix line editor and the book i'm reading says to type 1,$p
(also works in vim)
after trial and error I figured the first value means the line number but whats the purpose to $p? from what i can tell is the 1 goes to the beginning of the line and $p goes to the EOF and displays to me everything it picked up. is this true or am i way off?
The 1,$ part is a range. The comma separates the beginning and end of the range. In this case, 1 (line 1) is the beginning, and $ (EOF) is the end. The p means print, which is the command the range is being given to, and yes.. it displays to you what is in that range.
In vim you can look at :help :range and :help :print to find out more about how this works. These types of ranges are also used by sed and other editors.
They probably used the 1,$ terminology in the tutorial to be explicit, but note that you can also use % as its equivalent. Thus, %p will also print all the lines in the file.
Related
I want to do substitution using sed command, but also this should not get applied to specific range of lines matching some pattern/word.
File has below lines:
startline1
top
nice
good
bad
nice
nice
verybad
Extremely bad
bad
nice
endline1
nice
Trail from my end:
sed -i '/startline1/,/endline1/ s/nice/decent/'
The substitute must happen between startline1 and endline1
But must exclude the update between bad and verybad words
With the above command I get result as below:
startline1
top
decent
good
bad
decent
decent
verybad
Extremely bad
bad
decent
endline1
nice
This will do a replacement between only startline1 and endline1, which is as expected.
But now I don't want the update to happen between bad and verybad.
Expected Result:
startline1
top
decent
good
bad
nice
nice
verybad
Extremely bad
bad
decent
endline1
nice
Any suggestion on this would be of help.
Could you please try following based on your shown samples. Tested successfully in link
https://ideone.com/zFl8LK
awk '
/^bad$/{
bad_found=1
}
/^verybad$/{
bad_found=""
}
/endline1/{
found=""
}
/startline1/{
found=1
}
found && !bad_found{
sub(/nice/,"decent")
}
1
' Input_file
Explanation: Firstly checking condition if line starts with bad set bad_found then checking if line starts with verybad then unset bad_found there
checking condition if a line contains endline1 then unset found(found variable work is explained further in explanation). Then checking condition if line contains startline1 then setting variable found here. Then checking condition if found is set AND variable bad_found is NOT SET then substitute nice with decent in that line(where found makes sure that we are boundaries of startling to endline). Finally mentioning 1 will print all lines.
Note: in case your Input_file's lines may have more than 1 occurrence of nice then change sub to gsub in above code.
This might work for you (GNU sed):
sed '/\n/bb;/^start/{:a;N;/^end/M!ba;:b;/^bad.*verybad/h;x;/./{/^verybad/z;s/[^\n]*\n//;x;P;D};x;s/^nice/decent/;P;D}' file
The lines between start and end are gathered up and then processed a line at a time.
If the pattern space contains multiple lines i.e. the lines have already been gathered up, a flag is set in the hold space by copying the current pattern space if the first of these lines contains bad and there follows a line that contains verybad.
If the hold space is not empty and first line is verybad the hold space is emptied, thus flipping the s/nice/decent/ on.
N.B. The solution uses the control flow that the D command resumes processing the current multi line until the pattern space is empty.
If more that one nice occurs on a line use the idiom:
`:c;s/^\([^\n]*\)nice/\1decent/;tc`
currently I am working on comparison between SICStus3 and SICStus4 but I got one issue that is SICStus4 will not consult any cases where the comment string has carriage controls or tab characters etc as given below.
Example case as given below.It has 3 arguments with comma delimiter.
case('pr_ua_sfochi',"
Response:
answer(amount(2370.09,usd),[[01AUG06SFO UA CHI Q9.30 1085.58FUA2SFS UA SFO Q9.30 1085.58FUA2SFS NUC2189.76END ROE1.0 XT USD 180.33 ZPSFOCHI 164.23US6.60ZP5.00AY XF4.50SFO4.5]],amount(2189.76,usd),amount(2189.76,usd),amount(180.33,usd),[[fua2sfs,fua2sfs]],amount(6.6,usd),amount(4.5,usd),amount(0.0,usd),amount(18.6,usd),lasttktdate([20061002]),lastdateafterres(200712282]),[[fic_ticketinfo(fare(fua2sfs),fic([]),nvb([]),nva([]),tktiss([]),penalty([]),tktendorsement([]),tourinfo([]),infomsgs([])),fic_ticketinfo(fare(fua2sfs),fic([]),nvb([]),nva([]),tktiss([]),penalty([]),tktendorsement([]),tourinfo([]),infomsgs([]))]],<>,<>,cat35(cat35info([])))
.
02/20/2006 17:05:10 Transaction 35 served by static.static.server1 (usclsefat002:7551) running E*Fare version $Name: build-2006-02-19-1900 $
",price(pnr(
user('atl','1y',<>,<>,dept(<>,'0005300'),<>,<>,<>),
[
passenger(adt,1,[ptconly(n)])
],
[
segment(1,sfo,chi,'ua','<>','100',20140901,0800,f,20140901,2100,'737',res(20140628,1316),hk,pf2(n,[],[],n),<>,flags(no,no,no,no,no,no,no,no,no)),
segment(2,chi,sfo,'ua','<>','101',20140906,1000,f,20140906,1400,'737',res(20140628,1316),hk,pf2(n,[],[],n),<>,flags(no,no,no,no,no,no,no,no,no))
]),[
rebook(n),
ticket(20140301,131659),
dbaccess(20140301,131659),
platingcarrier('ua'),
tax_exempt([]),
trapparm("trap:ffil"),
city(y)
])).
The below predicate will remove comment section in above case.
flatten-cases :-
getmessage(M1),
write_flattened_case(M1),
flatten-cases.
flatten-cases.
write_flattened_case(M1):-
M1 = case(Case,_Comment,Entry),!,
M2 = case(Case,Entry),
writeq(M2),write('.'),nl.
getmessage(M) :-
read(M),
!,
M \== end_of_file.
:- flatten-cases.
Now my requirement is to convert the comment string to an ASCII character list.
Layout characters other than a regular space cannot occur literally in a quoted atom or a double quoted list. This is a requirement of the ISO standard and is fully implemented in SICStus since 3.9.0 invoking SICStus 3 with the option --iso. Since SICStus 4 only ISO syntax is supported.
You need to insert \n and \t accordingly. So instead of
log('Response:
yes'). % BAD!
Now write
log('Response:\n\tyes').
Or, to make it better readable use a continuation escape sequence:
log('Response:\n\
\tyes').
Note that using literal tabs and literal newlines is highly problematic. On a printout you do not see them! Think of 'A \nB' which would not show the trailing spaces nor trailing tabs.
But there are also many other situations like: Making a screenshot of program text, making a photo of program text, using a 3270 terminal emulator and copying the output. In the past, punched cards. The text-mode when reading files (which was originally motivated by punched cards). Similar arguments hold for the tabulator which comes from typewriters with their manually settable tab stops.
And then on SO it is quite difficult to type in a TAB. The browser refuses to type it (very wisely), and if you copy it in, you get it rendered as spaces.
If I am at it, there is also another problem. The name flatten-case should rather be written flatten_case.
For example, I have many HTML tabs to style, they use different classes, and will have different backgrounds. Background images files have names corresponding to class names.
The way I found to do it is yank:
.tab.home {
background: ...home.jpg...
}
then paste, then :s/home/about.
This is to be repeated for a few times. I found that & can be used to repeat last substitute, but only for the same target string. What is the quickest way to repeat a substitute with different target string?
Alternatively, probably there are more efficient ways to do such a thing?
I had a quick play with some vim macro magic and came up with the following idea... I apologise for the length. I thought it best to explain the steps..
First, place the text block you want to repeat into a register (I picked register z), so with the cursor at the beginning of the .tab line I pressed "z3Y (select reg z and yank 3 lines).
Then I entered the series of VIM commands I wanted into the buffer as )"zp:.,%s/home/. (Just press i and type the commands)
This translate to;
) go the end of the current '{}' block,
"zp paste a copy of the text in register z,
.,%s/home/ which has two tricks.
The .,% ensures the substitution applies to everything from the start of the .tab to the end of the closing }, and,
The command is incomplete (ie, does not have a at the end), so vim will prompt me to complete the command.
Note that while %s/// will perform a substitution across every line of the file, it is important to realise that % is an alias for range 1,$. Using 1,% as a range, causes the % to be used as the 'jump to matching parenthesis' operator, resulting in a range from the current line to the end of the % match. (which in this example, is the closing brace in the block)
Then, after placing the cursor on the ) at the beginning of the line, I typed "qy$ which means yank all characters to the end of the line into register q.
This is important, because simply yanking the line with Y will include a carriage return in the register, and will cause the macro to fail.
I then executed the content of register q with #q and I was prompted to complete the s/home/ on the command line.
After typing the replacement text and pressing enter, the pasted block (from register z) appeared in the buffer with the substitutions already applied.
At this point you can repeat the last #qby simple typing ##. You don't even need to move the cursor down to the end of the block because the ) at the start of the macro does that for you.
This effectively reduces the process of yanking the original text, inserting it, and executing two manual replace commands into a simple ##.
You can safely delete the macro string from your edit buffer when done.
This is incredibly vim-ish, and might waste a bit of time getting it right, but it could save you even more when you do.
Vim macro's might be the trick you are looking for.
From the manual, I found :s//new-replacement. Seemed to be too much typing.
Looking for a better answer.
I have the following in my .zshrc:
setopt PROMPT_SUBST
precmd(){
echo""
LEFT="$fg[cyan]$USERNAME#$HOST $fg[green]$PWD"
RIGHT="$fg[yellow]$(date +%I:%M\ %P)"
RIGHTWIDTH=$(($COLUMNS-${#LEFT}))
echo $LEFT${(l:$RIGHTWIDTH:)RIGHT}
}
PROMPT="$ "
This gives me the following screenshot
The time part on the right is always not going all the way to the edge of the terminal, even when resized. I think this is due to the $(date +%I:%M\ %P) Anyone know how to fix this?
EDIT: Zoomed in screenshot
While your idea is commendable, the problem you suffer from is that your LEFT and RIGHT contains ANSI escape sequences (for colors), which should be zero-width characters, but are nevertheless counted toward the length of a string if you naively use $#name, or ${(l:expr:)name}.
Also, as a matter of style, you're better off using Zsh's builtin prompt expansion, which wraps a lot of common things people may want to see in their prompts in short percent escape sequences. In particular, there are builtin sequences for colors, so you don't need to rely on nonstandard $fg[blah].
Below is an approximate of your prompt written in my preferred coding style... Not exactly, I made everything super verbose so as to be understandable (hopefully). The lengths of left and right preprompts are calculated after stripping the escape sequences for colors and performing prompt expansion, which gives the correct display length (I can't possibly whip that up in minutes; I ripped the expression off pure).
precmd(){
local preprompt_left="%F{cyan}%n#%m %F{green}%~"
local preprompt_right="%F{yellow}%D{%I:%M %p}%f"
local preprompt_left_length=${#${(S%%)preprompt_left//(\%([KF1]|)\{*\}|\%[Bbkf])}}
local preprompt_right_length=${#${(S%%)preprompt_right//(\%([KF1]|)\{*\}|\%[Bbkf])}}
local num_filler_spaces=$((COLUMNS - preprompt_left_length - preprompt_right_length))
print -Pr $'\n'"$preprompt_left${(l:$num_filler_spaces:)}$preprompt_right"
}
PROMPT="$ "
Edit: In some terminal emulators, printing exactly $COLUMN characters might wrap the line. In that case, replace the appropriate line with
local num_filler_spaces=$((COLUMNS - preprompt_left_length - preprompt_right_length - 1))
End of edit.
This is very customizable, because you can put almost anything in preprompt_left and preprompt_right and still get the correct lengths — just remember to use prompt escape sequence for zero width sequences, e.g., %F{}%f for colors, %B%b for bold, etc. Again, read the docs on prompt expansion: http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html.
Note: You might notice that %D{%I:%M %p} expands to things like 11:35 PM. That's because I would like to use %P to get pm, but not every implementation of strftime supports %P. Worst case scenario: if you really want lowercase but %P is not supported, use your original command subsitution $(date +'%I:%M %P').
Also, I'm using %~ instead of %/, so you'll get ~/Desktop instead of /c/Users/johndoe/Desktop. Some like it, some don't. However, as I said, this is easily customizable.
I'm trying to learn how to configure my .vimrc file with my own functions.
I'd like to write a function that traverses every line in a file and counts the total number of characters, but ignores all whitespace. This is for a programming exercise and as a stepping stone to more complex programs (I know there are other ways to get this example value using Vim or external programs).
Here's what I have so far:
function countchars()
let line = 0
let count = 0
while line < line("$")
" update count here, don't count whitespace
let line = getline(".")
return count
endfun
What functional code could I replace that commented line with?
If I understand the question correctly, you're looking to count the number of non-whitespace characters in a line. A fairly simple way to do this is to remove the whitespace and look at the length of the resulting line. Therefore, something like this:
function! Countchars()
let l = 1
let char_count = 0
while l <= line("$")
let char_count += len(substitute(getline(l), '\s', '', 'g'))
let l += 1
endwhile
return char_count
endfunction
The key part of the answer to the question is the use of substitute. The command is:
substitute(expr,pattern,repl,flags)
expr in this case is getline(l) where l is the number of the line being iterated over. getline() returns the content of the line, so this is what is being parsed. The pattern is the regular expression \s which matches any single whitespace character. It is replaced with '', i.e. an empty string. The flag g makes it repeat the substitute as many times as whitespace is found on the line.
Once the substitution is complete, len() gives the number of non-whitespace characters and this is added to the current value of char_count with +=.
A few things that I've changed from your sample:
The function name starts with a capital letter (this is a requirement for user defined functions: see :help user-functions)
I've renamed count to char_count as you can't have a variable with the same name as a function and count() is a built-in function
Likewise for line: I renamed this to l
The first line in a file is line 1, not line 0, so I initialised l to 1
The while loop counted up to but not including the last line, I assume you wanted all the lines in the file (this is probably related to the line numbering starting at 1): I changed your code to use <= instead of <
Blocks aren't based on indentation in vim, so the while needs an endwhile
In your function, you have let line = getline('.')
I added a ! on the function definition as it makes incremental development much easier (everytime you re-source the file, it will override the function with the new version rather than spitting out an error message about it already existing).
Incrementing through the file works slightly differently...
In your function, you had let line = getline('.'). Ignoring the variable name, there are still some problems with this implementation. I think what you meant was let l = line('.'), which gives the line number of the current line. getline('.') gives the contents of the current line, so the comparison on the while line would be comparing the content of the current line with the number of the last line and this would fail. The other problem is that you're not actually moving through the file, so the current line would be whichever line you were on when you called the function and would never change, resulting in an infinite loop. I've replaced this with a simple += 1 to step through the file.
There are ways in which the current line would be a useful way to do this, for example if you were writing a function with that took a range of lines, but I think I've written enough for now and the above will hopefully get you going for now. There are plenty of people on stackoverflow to help with any issues anyway!
Have a look at:
:help usr_41.txt
:help function-list
:help user-functions
:help substitute()
along with the :help followed by the various things I used in the function (getline(), line(), let+= etc).
Hope that was helpful.
This approach uses lists:
function! Countchars()
let n = 0
for line in getline(1,line('$'))
let n += len(split(line,'\zs\s*'))
endfor
return n
endfunction
I suppose you have already found the solution.
Just for info:
I use this to count characters without spaces in Vim:
%s/\S/&/gn