-
-
Notifications
You must be signed in to change notification settings - Fork 630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Re-thinking the command substitution operators #3924
Comments
Hi @daniel-shimon, this has been discussed (and rejected) several times already. The reasoning, if I remember correctly, was that the trailing newline is part of the captured output and therefore should be kept as is. What if the terminating newline character is significant to your pipeline? For instance Btw, I use There's a little trick using |
I understand what you're saying but it still doesn't feel very helpful. I'm talking of course only about subprocesses which are substituted inside another command, in which case I can't really see how that's helpful. In any case I guess this issue is a pretty opinionated thing if it has been discussed so many times. |
Yeah, it really is. The fundamental issue is that many CLIs add a trailing newline without detecting if they are connected to a TTY or not. Bash and other shells effectively add an rstrip operation when capturing (or really a split operation), effectively deciding what the output of subprocesses should be. I personally think that a lot of Bash's syntax and magic is particularly confusing to new users.
I wish it was. I think this is a case where configuration would be bad, because xonsh scripts would either run or fail based on how the environment was configured. It seems like it could cause a lot of downstream bugs. So I think that this is a scenario where a strong decision is needed.
Now this is an interesting idea. I am open to considering it. To-date all operators work exactly the same independent of their calling context. Things that would need to be fleshed out for this proposal.
I do like to keep an open mind, though! So I can be convinced away from the above opinions. |
@scopatz what if create distinct operator for this? |
That is what If anything I think we should either fully implement However, this was never fully done, so maybe it is better to drop the syntax entirely. |
Good to hear! I was fearing this is a dead end.
The basic jist is that If we're using |
I already have
instad of
I can open a PR if there's general interest in the functionality. |
@laloch could you please share your PR? I want to understand the process of creating additional operators =) |
Another option is to only have This resonates with |
Yeah I agree about not adding more syntax in this area |
Yeah, of course. Here you go: laloch/xonsh@98363df. It's pretty trivial, since it copies almost everything from |
It's not just for users coming from a different shell, it's really more useful and intuitive since you want information from some standard unix command (e.g. whoami, uname, nproc etc), and they all append newlines to their outputs. Here's another example from a classical daily shell use - |
I have only one concern #3394 :) |
#3394 is a bug - sometimes you need to call |
My main point here is that if we are going to make one subprocess operator modal (ie Python vs Subprocess), it is worth thinking about how all of them should behave in a modal fashion. Also, if as part of this, we should decide if we can get deprecate @anki-code's suggestion here gets close to that, but is probably not the complete answer. I guess what I want to see is a table with all 5 subprocess operators and how they would behave (ie what their return values would be) in both Python and subprocess mode. I think this would be a good exercise because such a tutorial would need to make it into the docs eventually anyway, if we proceeded down this path. |
The PR - #3926 |
@anki-code if I understand correctly with your suggestion This sounds like a pretty good idea to me - |
@scopatz In this suggestion, the |
I've edited the description of the solution. |
@anki-code very nice! |
In my opinion, |
@scopatz could you give an arguments why? |
Anyway, on the technical side, the original design was for So in this light, Now, I think there might be some confusion about what Bash does. In Bash, because everything is a string*, the In my opinion, I think using Like with Bash, splitting
This is why I think that So while I keep an open mind on what these various operators should return, I think we should also try to learn from some of the bad design of other shells. I hope this makes sense! *Yes, you can make arrays, but it is hard and annoying to do so. On the less technical side, I think there are a lot of things that we all want fixed before 1.0. This is another problem that probably should be resolved (one way or the other). Speaking only for myself, xonsh is something that I can only code on during the nights and weekends. I have other things to do in my free time as well. Because I am trying to support all parts of xonsh (review all PRs, put out releases, fix fundamental issues with the project, etc), I don't have as much time as even I would like to spend on each issue. There are other forks of xonsh out there. Which is great! Xonsh has made the process of creating high quality shells so much easier, that I am happy that people have made improvements in the direction that they want to go. Many years worth of effort have gone into xonsh from a fairly big group of people. I am sorry if you feel development is too slow. It is going as fast as it possibly can. An single person working on their own is always able to work faster than a group trying to make decisions. On the other hand, working to improve xonsh as a group, even though it is harder and slower, does improve the shell for everyone in the long run. If a change is important and valuable, it will make it in. But the bigger the effects of the change, like with subprocess syntax, the more people are going to need to weigh in on the pros/cons and the more forethought will need to be put into it. |
I agree we should improve the efficiency of the discussion, and document all the arguments in a centralized way. Following what you guys said, since this is such a major design decision in xonsh I suggest we take a step back from implementation details, gather concise examples, compare to other shells and document the pros-and-cons for each proposal. I'll try to make this issue a mini-PEP for the subject. I've updated the description to express all the things we've talked about. Let's take a few days to get more examples, discuss more options and let more people from the community express their opinions 😄 |
$(...)
$()
, !()
, etc)
$()
, !()
, etc)
Chiming in since I was one of those who was spam-mentioned in the other thread and because I was involved in the discussion and implementation of these operators the first time around, though I've not had a horse in this race for several years... (BTW, hi again, @scopatz et al! It's been a long time; I hope all is well!) Years ago when this conversation started, I was a proponent of stripping a trailing newline from the output of I'm not crazy about the implementation in #3926, since
This is the behavior I have in my fork, and I'm happy with it; I've never been surprised by what I get out of either of these operators, and examples like the I feel like this small change is also all that one needs to provide a relatively easy way to get just about anything you would want from a command. In Python mode:
Then, in terms of interpreting as arguments to a subprocess-mode command:
I feel like this structure, on the whole, is less work for the more common operations than the current behavior, and also when compared to the structure where Basically, I feel like the current behavior is mostly consistent, and that the single small change of r-stripping newlines by default in I don't have the time or the energy to jump into the fray here, though, other than to say that I am probably -1 on #3926 if I still get a vote, to agree with @daniel-shimon that these operators are indeed worth thinking about more, and to present my view of how I would want things to work. |
@scopatz I am in favor of removing We can blame bash for all we want but it is everywhere, even dockerfilss, CI configs etc, Copying the syntax but not the behaviour leds to confusion and frustration. I myself copy pasted code and expected it to run (my bad it was in bash😄). I understand that Xonsh wants to establish high standards. But come on, bash is not going to be replaced anytime soon and xonsh is primarily interactive usage (Though the scripts can be in xonsh, I find myself using plain python+sh. It is way more maintainable and has complete toolchain to lint,format etc.,). So why not make it more usable and less surprising. @anki-code Having said that, it is not fair to add such disruptive behaviour to an old project and have it by default. Many people would be having their scripts doing things using this behaviour. The best bet would be to make this a xontrib. Since this is a core code, making it modular, maybe it can be achieved. |
@jnoortheen thanks for your attention and time! The backwards compatibility is not a strong argument and xonsh is not so old (5 years slow xonsh development vs 15 years of fast fish development vs 30 years zsh/bash). Bash becomes ugly because thinks about backwards compatibility every day. |
Hello all! Hope your weekend going well. I made a lot of homework during the week of silence. I took into account all the comments and suggestions in this and other topics. I want to told you about the result.
Finally, I want to ask maintainers and any people who want to help read the XEP-2 proposal, try this approach in action and give a feedback. Feel free to report bugs and features to the XEP-2 repo. Thanks! PS: first feedback on XEP-2: |
After creating XEP-2 I want to read the @scopatz comment and answer more detailed.
This just an idea. You are not described the benefits behind or arguments why we should support this idea. We deleted
I agree with arguments against the IFS. But this can not be used as an argument that the Yes, IFS is bad because changes behavior of
Here you contradict yourself. You criticize the IFS above for this. No, we shouldn't change the
Rephrasing this questions: what
We all already agree that IFS is bad and we should choose the good unchangeable behavior for We still not have an arguments why it should be a string.
This statement tells to us that we should have an ability to do this. But it does not tell to us that we should use There are four counter arguments against that
As result the statement about explicitly splitting still has no arguments about why I want to additionally demonstrate that current body = '1\n2\r\r\r\r3\r\n4\n6\r7'
$(echo -n @(body)) == body
#False
len($(echo -n @(body))) == len(body)
#False
$(echo -n @(body))
#'1\n2\n\n\n\n3\n4\n6\n7'
echo -n $(echo -n @(body)) | md5sum
# 8dae88fa7c93e91a59abea2686e64994 -
echo -n @(body) | md5sum
# 35364d7e6a46e5f560e2e9509be31349 -
# Prepared by xontrib-hist-format This means that current
This statement:
I commented on Anthony's answer line by line and showed the lack of arguments not to attack him. Anthony is the founder of this incredibly awesome project and his opinion requires special attention. In this case this opinion does not have strong support and I just wanted to show it and urge all of us to what is included in the title of this issue - thinking. I'm looking forward to XEP-2 review from Anthony and xonsh/xore. |
Given XEP-2 says that it is backwards incompatible (which it is) and you want a xonsh2, I don't know how to approach this in any short time frame. |
That is to say, is there any way to change this so that it is actionable in the near future? Maybe have something which is mostly backwards compatible, so that we can move toward 1.0 with whatever changes we do want to make. |
I think the decision should be left to user, instead of forcing them to adapt to a behaviour one seem fit. |
@scopatz Why is backwards compatibility a big issue before we're at 1.0? If it's because of the time frame maybe we can split tasks to a few parallel PRs? |
Yeah, I 100% agree that we are not at v1.0 yet and so we should feel free to make backwards-incompatible changes. However, I don't know that we can fundamentally break every xonsh script & module that has ever been written. I would also like to avoid Python 2 ->3 transition issues, especially in pre-1.0 world. I guess we could use the current parser to translate older APIs into the newer ones, if we did want to go down these route. |
Also, is there a place I should be giving specific feedback on XEP-2? |
If you want to ask questions or point out to grey zones that should be described or improved in the proposal the best way is to create an issue in the repo. I'll think about it. We already have a question from laloch that was answered. Other thoughts feel free to put in any place you want. PS: it will be great to know who is from xore team also have an interest in this process and from who we can wait a feedback. |
Minute of statistics. I've counted the meaning of
As result this statistics on the code that written by xonsh maintainers and contributors completely supports the XEP-2 changes where we use: |
@anki-code I like your proposal, especially as remembering the meaning of all the different command substitution operators is the most difficult part of using xonsh IMO. It doesn't look like there's been any progress or discussion for a few years, is there any plan to move forward with this proposal or is something blocking it? |
I have no time to implement this but I still believe in this. There are no direct blockers. |
This seems like a really good proposal. |
Great proposal. Xonsh's current 5+ expansion operators seem redundant. Question: Do we need a separate operator for uncaptured subprocess? I'm new to xonsh, so I'm not familiar with all of the use cases, but at first glance it doesn't look like there is much use for an uncaptured subprocess operator. In python mode, if you don't need to use the value of a $() subprocess result, then just don't use it and it will be removed when the scope ends. If you need to see stdout in the terminal, then In subproc mode, there doesn't seem to be a purpose to uncaptured subprocesses at all, over just making separate statements |
The xonsh tutorial shows different use cases. In addition I would say that |
### Motivation * To have an ability to manage the output format added ``$XONSH_SUBPROC_OUTPUT_FORMAT`` to switch the way to return the output lines. Default ``stream_lines`` to return text. Alternative ``list_lines`` to return the list of lines. Also supported custom lambda function. * Additionally the [proposal to change default behavior](#5377 (comment)) for a single line case. * Closes #3924 as soft solution. ### Before ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $(ls) # '1\n2\n3\n' id $(whoami) # id: ‘pc\n’: no such user: Invalid argument du $(ls) # du: cannot access '1'$'\n''2'$'\n''3'$'\n': No such file or directory ls $(fzf) # ls: cannot access 'FUNDING.yml'$'\n': No such file or directory ``` ### After ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines' $(ls) # ['1', '2', '3'] [f for f in $(ls)] # ['1', '2', '3'] id $(whoami) # uid=501(user) gid=20(staff) du $(ls) # 0 1 # 0 2 # 0 3 ls $(fzf) # FUNDING.yml # etc mkdir -p /tmp/@($(whoami))/dir cat /etc/passwd | grep $(whoami) ``` ### Notes * It will be good to improve parser for cases like `mkdir -p /tmp/$(whoami)/dir`. PR is welcome! * I named the default mode as `stream_lines` (instead of just `stream` or `raw`) because in fact we transform raw output into stream of lines and possibly reduce the length of output ([replacing `\r\n` to `\n`](https://github.com/xonsh/xonsh/blob/c3a12b2a9c2958ce2a8e97a59b41f030f24cb45c/xonsh/procs/pipelines.py#L380-L383)). May be some day we need to add raw "stream" output format. * Now anybody can implement bash `IFS` behavior in [bashisms](https://github.com/xonsh/xontrib-bashisms). --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
To notify everybody from this epic thread with good news. |
Abstract
This is a proposal for re-designing the command substitution (a.k.a command pipelines) mechanisms in xonsh.
A simple example for command substitution in bash:
NOTE: This example is very basic and naïve. Please look at the usage examples below.
Motivation
Command substitutions have a range of usage scenarios, and we want them all to be easy and concise.
These usages differ by two factors - stripping and splitting.
Some of the more common combinations aren't easily expressed in xonsh currently.
Examples for each scenario (commands with the corresponding output format):
whoami
,uname
,hostname
,nproc
echo -n ... | wc -l
find
,git branch/log/diff --name-only
groups
,yay -Qdtq
,pkg-config
parsing /etc/passwd
Current xonsh behavior
$()
!()
@$()
$[]
![]
Current state and comparisons with other shells
The first three options are the more common ones according to our current examples.
@($(CMD).rstrip('\n'))
"$(CMD)"
@($(CMD).splitlines('\n'))
(CMD)
@$(CMD)
$(CMD)
$(CMD)
(CMD | string split ' ' --no-empty)
$(CMD)
@($(CMD).strip())
"$(CMD)"
@($(CMD).split('\n'))
@($(CMD).strip().split('\n'))
(CMD | string trim | string split ' ' --no-empty)
@($(CMD).rstrip('\n').split(TOKEN))
(CMD | string split TOKEN --no-empty)
@($(CMD).split(TOKEN))
@($(CMD).strip().split(TOKEN))
(CMD | string split TOKEN --no-empty | string trim)
NOTE: As far as I can tell, none of the shells has an option to only strip the single last newline.
Current proposals
XEP-2 (by @anki-code)
See details and examples in the XEP - https://github.com/anki-code/xonsh-operators-proposal/blob/main/XEP-2.rst.
In short:
!() removed.UPD: discussion.![] removed.UPD: discussion.CommandPipeline
(CP) class changes:CP.out
.CP.out
,CP.lines
andCP.__iter__
.$().split()
will returnCP.out.split()
that is IFS analogue in fact.$().lines_find(txt)
will return[l.find(txt) for l in CP.lines]
.This issue's initial proposal with influence from @adqm
$()
.!().__iter__
(i.e. use.splitlines()
).Pros:
!()
is consistantly iterated through.@$()
).!().out
.Cons:
$()
might need to change if the trailing newline is important.laloch@98363df (by @laloch)
Add
@$[]
operator to split by newlines.Pros:
Cons:
@$()
and$[]
in a meaningful way.No split, Strip newline
scenario.Rejected proposals
Initial proposal in this issue
Run
.rstrip('\n')
in$()
.Cons:
Add a config option to strip
$()
Cons:
For community
⬇️ Please click the 👍 reaction instead of leaving a
+1
or 👍 commentThe text was updated successfully, but these errors were encountered: