| Newsgroups |
gmane.editors.sed.user |
| Message-ID |
<[email protected]> |
I received an email from Anders Granlund (in Sweden) pointing out a subtle error in the GNU sed manual definitions of t and T (T is a GNU extension). I've explored this and think Anders is right.
"Branch to label only if there has been a successful substitution since the last input line was read or conditional branch was taken." is the current wording in the GNU sed manual for t.
"Branch to label only if there has been a successful substitution since the last input line was read or conditional branch was run." seems to me a more correct wording.
The current definition for t and T ends with "taken" (ie, branch taken). The suggested change ends with "run" (ie, t or T run). By "conditional branch", it means t or T, and "branch was taken" means t or T branched.
The original description of sed (from 1970's) says: "The t function tests whether any successful substitutions have been made on the current input line; if so, it branches to 'label'; if not, it does nothing. The flag which indicates that a successful substitution has been executed is reset by: 1) reading a new input line, or 2) executing a t function." The phrase "executing a t function" seems consistent to me with "t was run", not "branch was taken". So that is one line of reasoning that the GNU sed manual seems in error.
The other line of reasoning is to look at what sed does. Here is a test script I made up to look at cases that might arise:
$ cat test1.sh
echo "Input to sed is always the string 'old'"
echo "----------------------------------------------------------"
echo "Cases 1-4 are easy, as flag does not get reset"
echo "----------------------------------------------------------"
echo "Case 1. s works, sets flag, so t branches past d"
echo "Case 1. Point is that t branches if flag is set"
echo old | sed "s/old/new/; t; d"
echo "----------------------------------------------------------"
echo "Case 2. s fails, flag not set, so T branches past d"
echo "Case 2. Point is that T branches if flag not set"
echo old | sed "s/---/new/; T; d"
echo "----------------------------------------------------------"
echo "Case 3. s fails, flag not set, so t does not branch"
echo "Case 3. Point is that t does not branch if flag not set"
echo old | sed "s/---/new/; t; d"
echo "----------------------------------------------------------"
echo "Case 4. s works, sets flag, so T does not branch"
echo "Case 4. Point is that T does not branch if flag set"
echo old | sed "s/old/new/; T; d"
echo "----------------------------------------------------------"
echo "Cases 5-8 see if flag gets reset after s works"
echo "----------------------------------------------------------"
echo "Case 6. s works, tx branches, flag reset, t fails"
echo "Case 6. Point is that t flag is reset if t branches"
echo old | sed "s/old/new/; tx; d; :x t; l"
echo "----------------------------------------------------------"
echo "Case 6. s works, tx branches, flag reset, T branches"
echo "Case 6. Point is that T flag is reset if t branches"
echo old | sed "s/old/new/; tx; d; :x T; l"
echo "----------------------------------------------------------"
echo "Case 7. s works, Tx fails, flag reset, T branches"
echo "Case 7. Point is that T flag is reset if T fails"
echo old | sed "s/old/new/; Tx; l; :x T; d"
echo "----------------------------------------------------------"
echo "Case 8. s works, Tx fails, flag reset, t fails"
echo "Case 8. Point is that t flag is reset if T fails"
echo old | sed "s/old/new/; Tx; l; :x t; d"
echo "----------------------------------------------------------"
echo "Cases 9-10 see if flag gets reset by s just running"
echo "----------------------------------------------------------"
echo "Case 9. s1 works, s2 fails, but t still branches due to s1"
echo "Case 9. Point is that flag is not reset by s just running"
echo old | sed "s/old/new/; s/---/new/; t; d"
echo "----------------------------------------------------------"
echo "Case 10. s1 works, s2 fails, but T still fails due to s1"
echo "Case 10. Point is that flag is not reset by s just running"
echo old | sed "s/old/new/; s/---/new/; T; d"
echo "----------------------------------------------------------"
Here is what results:
$ ./test1.sh
Input to sed is always the string 'old'
----------------------------------------------------------
Cases 1-4 are easy, as flag does not get reset
----------------------------------------------------------
Case 1. s works, sets flag, so t branches past d
Case 1. Point is that t branches if flag is set
new
----------------------------------------------------------
Case 2. s fails, flag not set, so T branches past d
Case 2. Point is that T branches if flag not set
old
----------------------------------------------------------
Case 3. s fails, flag not set, so t does not branch
Case 3. Point is that t does not branch if flag not set
----------------------------------------------------------
Case 4. s works, sets flag, so T does not branch
Case 4. Point is that T does not branch if flag set
----------------------------------------------------------
Cases 5-8 see if flag gets reset after s works
----------------------------------------------------------
Case 6. s works, tx branches, flag reset, t fails
Case 6. Point is that t flag is reset if t branches
new$
new
----------------------------------------------------------
Case 6. s works, tx branches, flag reset, T branches
Case 6. Point is that T flag is reset if t branches
new
----------------------------------------------------------
Case 7. s works, Tx fails, flag reset, T branches
Case 7. Point is that T flag is reset if T fails
new$
new
----------------------------------------------------------
Case 8. s works, Tx fails, flag reset, t fails
Case 8. Point is that t flag is reset if T fails
new$
----------------------------------------------------------
Cases 9-10 see if flag gets reset by s just running
----------------------------------------------------------
Case 9. s1 works, s2 fails, but t still branches due to s1
Case 9. Point is that flag is not reset by s just running
new
----------------------------------------------------------
Case 10. s1 works, s2 fails, but T still fails due to s1
Case 10. Point is that flag is not reset by s just running
----------------------------------------------------------
The key cases are #7 and #8. In both cases, s substituted, so T did not branch. According to the current GNU sed manual, branching is required to reset the flag. But the flag was reset, so T branches next time it appears, and t does not branch next time it appears. Merely running t or T, even if no branch, is enough to reset the flag.
I don't think this is a big deal. It seems rather obscure, something that had never crossed my mind until Anders found it. I would always put t or T immediately after the s command, so cases 7 and 8 would rarely or never occur in practice. It seems like asking for trouble to have multiple t/T commands scattered about. But who knows? Maybe this might come up in some games that get programmed in complex sed scripts.
Whether this is good style or not, the point is the current wording seems incorrect. If the manual is incorrect, I hope it can get fixed. If the current wording is OK, I'd be interested to hear what Anders and I missed.
Thanks,
Daniel