-
Notifications
You must be signed in to change notification settings - Fork 402
Properly handle environments with unset IFS #687
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
Conversation
|
Thank you for the PR! I think we should finally merge this PR. There are two things that should be addressed. 1. unset -vWe are making it explicit whether the 2. dynamic unset
$ function func { declare -p IFS; local IFS=b; declare -p IFS; unset -v IFS; declare -p IFS; }
$ IFS=a; func
declare -- IFS="a"
declare -- IFS="b"
declare -- IFSIn order to remove the local variable of the current scope and to make the variable of the previous context visible (dynamic unset), one needs to let $ function _comp_unlocal { unset -v "$@"; }
$ function func { declare -p IFS; local IFS=b; declare -p IFS; _comp_unlocal IFS; declare -p IFS; }
$ IFS=a; func
declare -- IFS="a"
declare -- IFS="b"
declare -- IFS="a"In Bash 5.0+, one additionally needs to care about |
|
Mind blown on that _comp_unlocal function. Let me see if I understand why it works:
Is this correct understanding? Is there somewhere in the docs I can read more? |
|
Ah, I think I first should have explained that the variable scoping in Bash is dynamic scoping like Perl's
Also, it should be noted that we need to differentiate three different situations for a variable in a specific scope: 1) a value is set, 2) the variable is unset, and 3) the variable does not exist. Case 2 is different from case 1 with an empty value Then the behavior of
In short, when the |
|
I misunderstood the key sentence: Looking at the source code, there is more explanation: So how stable is this? Would it be better to just handle the unset global IFS case explicitly? |
AFAIK, the behavior is consistent at least from Bash 3.0 except for the recent addition of You can find a long discussion at
Could you describe in more detail about what you mean by "the unset global IFS case"? |
|
Sounds stable enough! |
|
Updated. Is there a better place to slap that unit test? |
|
Oops. Messed up the version check. Fixed. |
akinomyoga
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for updating! Could you see the following comments?
akinomyoga
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just suggestions of cosmetic changes
|
Ok, I think I have all of the feedback folded in. I held off on the _comp_shopt_restore stuff, as that makes more sense to me as a separate commit. |
|
Thank you! Now it LGTM. Let's run the CI test now. Can I reopen these two unresolved conversations although they should be addressed in separate PRs? This is just because I wanted to later work on them and would like to keep attention to them. Otherwise, I can possibly forget them. |
akinomyoga
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sudonym1 It seems the pre-commit hook fails. Can you set pre-commit up and fix the issues? The way to set it up is a bit non-trivial but you can reference the comment at #683 (comment).
Prior to this commit, the current state of `IFS` was stored into a local variable `ifs`. Later, IFS is restored by setting `IFS=$ifs`. If the user starts with either a set `IFS` or an empty `IFS`, then this behavior is correct. If the user starts with an unset `IFS`, then `IFS=$ifs` results in an empty `IFS`, which is not the original state. Most of the time an empty `IFS` is not desired, and indeed it breaks some things inside bash-completions. eg ``` unset IFS local ifs=$IFS IFS=$' \t\n' restore=$(shopt -po noglob) IFS=$ifs $restore ... -bash: set -o noglob: command not found ``` With this commit, all instances of `local ifs=$IFS` have been removed. Instead, the `local IFS` is dynamically unset.
|
Oops. Went and fixed the lints before I saw your comment. Will recheck with the tool you mention. |
|
Ok, my environment doesn't support docker ATM so I ran the shfmt tool manually. Everything seems to pass now. |
|
OK, Thanks! I rerun the CI test. |
Of course! |
|
Somehow the test in @scop Do you have any comments? |
|
No other comments besides impressive work, thanks! Tests seem flaky at the moment, I've kicked off a re-run, feel free to merge at your discretion whether they fail or not if the failures seem unrelated. |
Is there a ticket for this? I wouldn't mind taking a look this upcoming weekend. |
|
Thanks, I have merged it.
Thank you! I don't remember any. I have quickly searched, but there seems none explicitly mentioning this issue. #136 and #137 seem to be the timeouts caused by the test case itself. I'm not sure whether the cause of #139 is the test case itself or our test framework. There is a report #430 saying that the test fails or is flaky in ARM architecture with high loads but there is no further information. #573 is related to refactoring the test suite. I couldn't find any issue explicitly mentioning the timeout failure. |
|
Timeouts are a quite general failure mode in the test suite. Sometimes they occur when tests fail to process expected/unexpected output or the lack of it, kind of masking the root cause. These are fairly reproducible and straightforward to fix. Sometimes they occur due to something else, perhaps a command actually stalling every now and then, this would be hard to reproduce and fix. But the most "interesting" thing to me (and the one I guess we're referring to) here is that I think we may still have something wrong in our test suite that triggers this in unknown situations that is not caused by the things mentioned here. Would be awesome to track these down and fix this category of issues. I believe enabling parallel tests provokes the failures more often -- at least it does provoke some failures more. Parallel running happens by default in local dockerized test runs, and can be enabled on the host without docker e.g. like $ PYTESTFLAGS="--numprocesses=auto --dist=loadfile" make check |
|
It seems I have been getting (un)lucky. I've run the fedora docker container ~10 times with parallel pytests, and no failures so far. I will try a different approach. It could be these issues are related to the performance characteristics of the host computer. |
|
I guess the problem is caused by some race conditions or the synchronization problem of input and output pipes in the communications between Actually, I'm not sure if the patterns specified here covers all the possible cases of the Bash outputs. What happens when some garbage outputs remained in the stdout before writing something to stdin (though I'm not even sure whether that can happen because we are checking MAGIC_MARK between every roundtrip)? The accepted patterns in the current testing code are
I feel we should just accept |
|
Maybe we can create a new issue or a Draft PR for this timeout issue of the test suite. |
This removes some ineffective IFS settings IFS=$'\0' found in scop#687 [1]. The quoted strings are not subject to the word splitting, where IFS does not have effects. The setting IFS=$'\0' has been first introduced in 99b2894, and the related codes have been updated in 1c4b461, 2f61acd, 4375c4b, and f241252. In the change history, the setting IFS=$'\0' does not seem to have had any effect at any point, so we just remove the setup IFS=$'\0' today. The original intent is actually not so clear. The related code has been first introduced to process octal escape sequences of the form `\ooo` (where `o` is a digit 0-7) used in `/etc/fstab` [2]. One possibile intent might have been to split the filesystem names by NUL represented by `\000` in `/etc/fstab`, but we cannot find any information on the special meaning of NUL in `/etc/fstab`. We think the intent has probably been to make sure that the word splitting would not happen, which was actually not needed for the quoted strings. [1] scop#687 [2] man fstab (found online at e.g. https://linux.die.net/man/5/fstab)
This removes some ineffective IFS settings IFS=$'\0' found in #687 [1]. The quoted strings are not subject to the word splitting, where IFS does not have effects. The setting IFS=$'\0' has been first introduced in 99b2894, and the related codes have been updated in 1c4b461, 2f61acd, 4375c4b, and f241252. In the change history, the setting IFS=$'\0' does not seem to have had any effect at any point, so we just remove the setup IFS=$'\0' today. The original intent is actually not so clear. The related code has been first introduced to process octal escape sequences of the form `\ooo` (where `o` is a digit 0-7) used in `/etc/fstab` [2]. One possibile intent might have been to split the filesystem names by NUL represented by `\000` in `/etc/fstab`, but we cannot find any information on the special meaning of NUL in `/etc/fstab`. We think the intent has probably been to make sure that the word splitting would not happen, which was actually not needed for the quoted strings. [1] #687 [2] man fstab (found online at e.g. https://linux.die.net/man/5/fstab)
From the bash man page:
Per this paragraph, IFS can have one of three states:
Prior to this commit, the current state of
IFSwas stored into a localvariable
ifs. Later, IFS is restored by settingIFS=$ifs. If theuser starts with either a set
IFSor a nullIFS, then this behavioris correct. If the user starts with an unset
IFS, thenIFS=$ifsresults in a NULL
IFS, which is not the original state. Most of thetime a null
IFSis not desired, and indeed it breaks some thingsinside bash-completions. eg
Above the line
IFS=$ifsputs us in a NULLIFSstate, which is notwhere we started. The line
$restorefails with a confusing errormessage
-bash: set -o noglob: command not found.In addition, this pattern does not make good use of bash shadowing
rules. When a
local IFSshadows the globalIFS, all that is neededto restore the global IFS is to unset the local one.
With this commit, all instances of
local ifs=$IFShave been removed.