41

I know that the cut command can print the first n characters of a string but how to select the last n characters?

If I have a string with a variable number of characters, how can I print only the last three characters of the string. eg.

"unlimited" output needed is "ted"
"987654" output needed is "654"
"123456789" output needed is "789"
jasonwryan
  • 71,734
  • 34
  • 193
  • 226
odyssey
  • 411
  • 1
  • 4
  • 3

12 Answers12

70

Why has nobody given the obvious answer?

sed 's/.*\(...\)/\1/'

… or the slightly less obvious

grep -o '...$'

Admittedly, the second one has the drawback that lines with fewer than three characters vanish; but the question didn’t explicitly define the behavior for this case.

  • 11
    or `grep -o '.\{3\}$'` – Avinash Raj Oct 22 '14 at 00:36
  • 2
    True, but, in this case, this "labor-saving device" requires more typing. :-) – G-Man Says 'Reinstate Monica' Oct 22 '14 at 00:37
  • 3
    or `echo "unlimited" | python -c "print raw_input()[-3:]"` – Kiro Oct 22 '14 at 07:29
  • 10
    @Kiro or `"echo unlimited" | java -jar EnterpriseWordTrimmer.jar`, but I don't think it's really necessary to bring in a heavier language for character manipulation. – wchargin Oct 22 '14 at 20:53
  • 13
    @WChargin you forgot `java -server -Xms300M -Xmx3G -XX:+UseParallelGC -cp /path/to/all/the/jars/ -Dinput.interactive=false -Dinput.pipe=true -Dconfig.file=/path/to/config/last-three-letters.cfg -jar ...` – h.j.k. Oct 23 '14 at 03:56
  • 2
    @h.j.k. We should review the memory requirements; And try to not get into a fight which GC options to use... – Volker Siegel Oct 23 '14 at 10:15
  • @wchargin +1 for reinforcing the "heavier language" issue. – Rodrigo Nov 20 '16 at 06:08
  • 12
    `grep -o -P '.{0,3}$'` will print last 3 characters even if the line has fewer than 3 characters. `-P` avoids having to escape the braces. – Raghu Dodda Dec 11 '16 at 19:41
  • 1
    Both of these invoke the external binary (`grep` or `sed`) which may well be slower if the user is already in a shell with the string in a variable. – DopeGhoti May 24 '19 at 20:38
  • Well yes, but the same is true for [jasonwryan’s answer](https://unix.stackexchange.com/q/163481/80216163483), [glenn jackman’s answer](https://unix.stackexchange.com/q/163481/80216163488), [Volker Siegel’s answer](https://unix.stackexchange.com/q/163481/80216163497), [Evgeny Vereshchagin’s answer](https://unix.stackexchange.com/q/163481/80216214982), [gildux’s answer](https://unix.stackexchange.com/q/163481/80216242833) and [Saurabh’s answer](https://unix.stackexchange.com/q/163481/80216429393). (And I upvoted [*your* answer](//unix.stackexchange.com/q/163481/80216163484) when you posted it.) – G-Man Says 'Reinstate Monica' May 24 '19 at 21:07
  • This works on every line when used in piping and is therefore better than alternatives imo. – confetti Apr 02 '20 at 02:47
  • 1
    @confetti: (1) Welcome back to Stack Exchange. I’ve missed seeing your colorful identicon. (2) I’m glad that you like my answer, but, [jasonwryan’s answer](https://unix.stackexchange.com/q/163481/80216#163483) also works on every line when used in piping, and [glenn jackman’s](https://unix.stackexchange.com/q/163481/80216#163488) can be trivially adapted to do so. – G-Man Says 'Reinstate Monica' Apr 02 '20 at 08:26
  • @G-ManSays'ReinstateMonica' And since we are at this: Both answers count characters as "unicode code-points". Characters that have been decomposed fail. To try: Set `var=$'unlimited\n987654\n123456789\n123αβγ\nαβγ\nβγ\nγ\n\na\U301e\U301i\U301'` and then try `echo "$var" | sed 's/.*\(...\)/\1/'` (for example). –  Mar 29 '22 at 00:23
57

Keeping it simple - tail

We should not need a regular expression, or more than one process, just to count characters.
The command tail, often used to show the last lines of a file, has an option -c (--bytes), which seems to be just the right tool for this:

$ printf 123456789 | tail -c 3
789

(When you are in a shell, it makes sense to use a method like in the answer of mikeserv, because it saves starting the process for tail.)

Real Unicode characters?

Now, you ask for the last three characters; That's not what this answer gives you: it outputs the last three bytes!

As long as each character is one byte, tail -c just works. So it can be used if the character set is ASCII, ISO 8859-1 or a variant.

If you have Unicode input, like in the common UTF-8 format, the result is wrong:

$ printf 123αβγ | tail -c 3
�γ

In this example, using UTF-8, the greek characters alpha, beta and gamma are two bytes long:

$ printf 123αβγ | wc -c  
9

The option -m can at least count the real unicode characters:

printf 123αβγ | wc -m
6

Ok, so the last 6 bytes will give us the last 3 characters:

$ printf 123αβγ | tail -c 6
αβγ

So, tail does not support handling general characters, and it does not even try (see below): It handles variable size lines, but no variable size characters.

Let's put it this way: tail is just right for the structure of the problem to solve, but wrong for the kind of data.

GNU coreutils

Looking further, it turns out that thee GNU coreutils, the collection of basic tools like sed, ls, tail and cut, is not yet fully internationalized. Which is mainly about supporting Unicode.
For example, cut would be a good candidate to use instead of tail here for character support; It does have options for working on bytes or chars, -c (--bytes) and -m (--chars);

Only that -m/--chars is, as of version
cut (GNU coreutils) 8.21, 2013,
not implemented!

From info cut:

`-c CHARACTER-LIST'
`--characters=CHARACTER-LIST'
     Select for printing only the characters in positions listed in CHARACTER-LIST.  
     The same as `-b' for now, but internationalization will change that.


See also this answer to Can not use `cut -c` (`--characters`) with UTF-8?.

Volker Siegel
  • 16,983
  • 5
  • 52
  • 79
  • 2
    Actually, most of the other answers seem to handle Unicode just fine, as long as the current locale specifies UTF-8 encoding. Only yours and glenn jackman's `cut`-based solution don't seem to. – Ilmari Karonen Oct 22 '14 at 06:32
  • @IlmariKaronen True, thanks for the hint. I have edited, with some additional detail. – Volker Siegel Oct 23 '14 at 10:12
  • 3
    Note that POSIX explicitly specifies that `tail` should deal with bytes, and not characters. I once made a patch to add a new option to also select characters, but I believe that never got merged :-/ – Martin Tournoij Jul 10 '15 at 10:59
  • Doesn't work in file-mode, like `tail -c3 -n10 /var/log/syslog` – Suncatcher May 13 '18 at 08:39
  • @Suncatcher I tried, and it worked. What is the problem you see? Your command `tail -c3 -n10 /var/log/syslog` asks for the last 10 lines, and that works for me. You use the option `-c3`, and after that the conflicting option `-n10`. The later option takes priority. – Volker Siegel May 13 '18 at 13:11
  • @Suncatcher `tail -c3 -n10 /var/log/syslog` also works, it returns the last 3 bytes. The last one should often be a newline. I in my case, the shell adds a `$` because the `\n`, which should be at the end of any text file, because the last line ends at the end of file. – Volker Siegel May 13 '18 at 13:20
  • And how to print last 3 chars of **each** line within last ten lines? What is the proper replacement for `-n10`? – Suncatcher May 13 '18 at 15:50
  • I solved my the task with `tail -n10 /var/log/syslog | cut -b 1-3` but just out of curiosity – Suncatcher May 13 '18 at 15:51
  • @Suncatcher The last three characters of lines? There is a very shell style-like, **so simple it's funny**: Reverse each line (with `rev`), cut the **first** three characters each line - they are in the opposite order - and reverse back the three characters from each line. `tail -n10 /var/log/syslog | rev | cut -b 1-3 | rev`. – Volker Siegel Jun 20 '18 at 13:07
  • Ooo, you have sick mind to do tricks like this :) But it works, ya! – Suncatcher Jun 20 '18 at 15:22
  • see this 2017 article, sub-titled *”Random notes and pointers regarding the on-going effort to add multibyte and unicode support in GNU Coreutils“*: https://crashcourse.housegordon.org/coreutils-multibyte-support.html – myrdd Dec 12 '18 at 14:30
38

If your text is in a shell variable called STRING, you can do this in a bash, zsh, mksh or busybox ash shell:

printf '%s\n' "${STRING:(-3)}"

Or

printf '%s\n' "${STRING: -3}"

which also has the benefit to work with ksh93 where that syntax comes from.

The point is that the : has to be separated from the -, otherwise it becomes the ${var:-default} operator of the Bourne shell.

The equivalent syntax in the zsh or yash shells is:

printf '%s\n' "${STRING[-3,-1]}"
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
DopeGhoti
  • 73,792
  • 8
  • 97
  • 133
  • 2
    What is that kind of syntax/operation called so I can search for more informarion? – Tulains Córdova Apr 15 '15 at 12:14
  • 8
    It's called **Substring Expansion**.  It's a kind of **Parameter Expansion**.  The general form is _${parameter:offset:length}_, but the _length_ field is optional  (and, as you can see, it has been omitted in the answer above).  DopeGhoti could also have written `${STRING:(-3):3}` (specifying the _length_ field), `${STRING: -3}` (with a space between the `:` and the `-`), or `${STRING: -3:3}`. – G-Man Says 'Reinstate Monica' Apr 15 '15 at 19:17
  • In this case, specifying the length of `3` is somewhat moot as that's asking for "the three characters from the third from the last character, inclusive" which happens to be an identical operation in practical terms to "All characters onward from the third from the last, inclusive". – DopeGhoti May 03 '18 at 22:24
  • Not for multiline var input. –  Mar 27 '22 at 22:32
  • 1
    @IsaaC: Huh? This answer outputs the last three characters in the string (which is what the question asks for) plus a newline (which is always implied).  The question doesn’t say anything about multi-line strings; this answer handles that unspecified case in a reasonable way. – G-Man Says 'Reinstate Monica' Mar 28 '22 at 21:13
  • DopeGhoti:  I would upvote your answer now, but I can’t, because I did so seven years ago. – G-Man Says 'Reinstate Monica' Mar 28 '22 at 21:13
  • I nonetheless appreciate the thought. – DopeGhoti Mar 28 '22 at 21:55
  • This answer (compared to your answer) doesn't work (correctly) with multiline files (of which a var is just a proxy). Set (in bash) `var=$'unlimited\n987654\n123456789\n123αβγ\nαβγ\nβγ\nγ\n\na\U301e\U301i\U301'` and then try `printf '%s\n' "${var:(-3)}"`. It prints only **one** line. @G-ManSays'ReinstateMonica'` –  Mar 29 '22 at 00:11
  • @G-ManSays'ReinstateMonica' And the question asks about **several** inputs (unlimited 987654 123456789) not **one** string. How is this answer addressing the **several** inputs ? –  Mar 29 '22 at 00:27
  • The examples in the OP of `unlimited`, `987654`, and `123456789` are expressly given as _seaparate_ data to act upon, not a combined multi-line dataset. e. g. `"987654" output needed is "654"` expressly states a single-line input of `987654`. – DopeGhoti Mar 29 '22 at 14:10
  • If that is what you think. –  Mar 30 '22 at 02:53
15

Using awk:

awk '{ print substr( $0, length() - 2) }' file
ted
654
789
αғsнιη
  • 40,939
  • 15
  • 71
  • 114
jasonwryan
  • 71,734
  • 34
  • 193
  • 226
  • Works for (some) utf-8 and for multiline input (+1). Warning: If a glyph (character) comes from a decomposed character then this fail. Make `a=$(printf '%b' a \\U301 e \\U301 i \\U301 $'\n')` (in bash, but could be easily translated to any shell). And try `<<<"$a" awk '{ print "substr( $0," length() "- 2)",$0, substr( $0, length() - 2) }'` –  Mar 27 '22 at 22:31
13

If the string is in a variable you can do:

printf %s\\n "${var#"${var%???}"}"

That strips the last three characters from the value of $var like:

${var%???}

...and then strips from the head of $var everything but what was just stripped like:

${var#"${var%???}"}

This method has its upsides and downsides. On the bright side it is fully POSIX-portable and should work in any modern shell. Also, if $var does not contain at least three characters nothing but the trailing \newline is printed. Then again, if you want it printed in that case, you need an additional step like:

last3=${var#"${var%???}"}
printf %s\\n "${last3:-$var}"

In that way $last3 is only ever empty if $var contains 3 or fewer bytes. And $var is only ever substituted for $last3 if $last3 is empty or unset - and we know it is not unset because we just set it.

mikeserv
  • 57,448
  • 9
  • 113
  • 229
  • That's pretty tidy +1. Aside: any reason you don't quote your `printf` format strings? – jasonwryan Oct 21 '14 at 23:30
  • Why not just use `${VARNAME:(-3)}` (presuming `bash`)? – DopeGhoti Oct 21 '14 at 23:34
  • @jasonwryan - only because `%s\\n` is shorter to write than is `'%s\n'`. I do of course quote them when they contain expansions or characters the shell might misinterpret - like when I use math expansions for string truncation - but simple ones like this I just quote what is necessary - which is to say, I quote the backslash. – mikeserv Oct 21 '14 at 23:36
  • 1
    Thanks for clarifying; makes sense, even if it looks (to me) a little odd... – jasonwryan Oct 21 '14 at 23:38
  • 3
    @DopeGhoti - simply because that is an assumption I almost never make. This works as well in `bash` as in any other shell claiming POSIX comapibility. – mikeserv Oct 21 '14 at 23:39
  • Fair enough, @mikeserv – DopeGhoti Oct 21 '14 at 23:40
  • 3
    @odyssey - The problem is `csh` is *not* among the *modern, POSIX-compatible* shells I mention here, unfortunately. The POSIX-shell spec is modeled after `ksh`, which modeled itself after a combination of both `csh` and the traditional Bourne-style shells. `ksh` incorporated both `csh`'s excellent job-control functionality and the old Bourne-styles' i/o redirection. It also added some things - such as the string manipulation concepts I demonstrate above. This will not likely work in any traditional `csh` as far as I know, I am sorry to say. – mikeserv Oct 22 '14 at 01:07
  • oops..... I deleted my own comment. Thanks for helping out all the same. Cheers – odyssey Oct 22 '14 at 01:09
  • Of course, this works only on the last three characters of the last line, even if `$VARNAME` is multiline. –  Mar 27 '22 at 22:25
8

You can do this, but this is a little ... excessive:

words=( unlimited 987654 123456789 )

for s in "${words[@]}"; do
    rev <<< "$s" | cut -c 1-3 | rev
done 
ted
654
789
glenn jackman
  • 84,176
  • 15
  • 116
  • 168
3

The bulletproof solution for utf-8 strings:

utf8_str=$'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' # привет

last_three_chars=$(perl -CAO -e 'print substr($ARGV[0], -3)' -- "$utf8_str")

(the -- is critical here, or you'd introduce an arbitary command injection vulnerability).

Or use:

last_three_chars=$(perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' -- "$utf8_str")

to prevent the malformed data handling.

Example:

perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' -- $'\xd0\xd2\xc9\xd7\xc5\xd4' # koi8-r привет

Outputs something like this:

utf8 "\xD0" does not map to Unicode at /usr/lib/x86_64-linux-gnu/perl/5.20/Encode.pm line 175.

Doesn't depend on locale settings (i.e. works with LC_ALL=C). Bash, sed, grep, awk, rev require something like this: LC_ALL=en_US.UTF-8

Common solution:

  • Receive bytes
  • Detect encoding
  • Decode bytes to characters
  • Extract charaсters
  • Encode character to bytes

You can detect encoding with uchardet. See also related projects.

You can decode/encode with Encode in Perl, codecs in Python 2.7

Example:

Extract last three characters from utf-16le string and convert these characters to utf-8

utf16_le_str=$'\xff\xfe\x3f\x04\x40\x04\x38\x04\x32\x04\x35\x04\x42\x04' # привет

chardet <<<"$utf16_le_str"  # outputs <stdin>: UTF-16LE with confidence 1.0

last_three_utf8_chars=$(perl -MEncode -e '
    my $chars = decode("utf-16le", $ARGV[0]);
    my $last_three_chars = substr($chars, -3);
    my $bytes = encode("utf-8", $last_three_chars);
    print $bytes;
  ' "$utf16_le_str"
)

See also: perlunitut, Python 2 Unicode HOWTO

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
Evgeny Vereshchagin
  • 5,286
  • 4
  • 35
  • 43
  • `echo` is your bulletproof source? – mikeserv Jul 10 '15 at 02:17
  • @mikeserv, `decode/encode` is my bulletproof source. Cleaned up my answer. – Evgeny Vereshchagin Jul 10 '15 at 09:44
  • This also depends on locale settings to guarantee that it works correctly, since a set of bytes may reflect different characters in different charsets. It "works" for `LC_ALL=C` because that's a very "dumb" setting, but it may break when you try to pass an UTF-8 string to SHIFT-5, or a SHIFT-5 string to KOI8, etc. – Martin Tournoij Jul 10 '15 at 11:02
  • @Carpetsmoker, thanks. Could you explain your comment? I suppose that `perl -CAO -e 'print substr($ARGV[0], -3)'` works fine. `A` the @ARGV elements are expected to be strings encoded in UTF-8, `O` STDOUT will be in UTF-8. – Evgeny Vereshchagin Jul 10 '15 at 11:11
  • looks like you tell about assignment to `utf8_str` – Evgeny Vereshchagin Jul 10 '15 at 11:18
  • @Carpetsmoker, in any case the "detect encoding" step tries to prevent interpreting `utf-8` as `koi8-r` – Evgeny Vereshchagin Jul 10 '15 at 11:53
  • 1
    You generally can't pass UTF-16 encoded strings as arguments to commands as the encoding of many characters (including all the ones in ASCII and latin-1) contain NUL bytes. – Stéphane Chazelas Mar 28 '22 at 08:17
1

What about using "expr" or "rev" ?

An answer similar the one provided by @G-Man : expr "$yourstring" : '.*\(...\)$' It has the same drawback than the grep solution.

A well known trick is to combine "cut" with "rev" : echo "$yourstring" | rev | cut -n 1-3 | rev

gildux
  • 141
  • 5
  • 1
    The `rev` solution looks a lot like [glenn jackman's](http://unix.stackexchange.com/a/163488/117549) – Jeff Schaller Nov 13 '15 at 15:45
  • You're right @Jeff_Schaller : I missed glenn's one :-( – gildux Nov 13 '15 at 17:27
  • The `expr` one doesn't work for values of `$yourstring` that start with `-` or are `expr` operators (or contain sequences of bytes that don't form valid characters). The `echo` one doesn't work for values of `$yourstring` that are valid combinations of `echo` options or with some `echo` implementations (including the ones that are UNIX compliant) that contain backslash characters. – Stéphane Chazelas Mar 28 '22 at 07:38
  • 1
    @StéphaneChazelas: (1) In ```expr``` version 8.23, I can’t get it to fail for values of `$yourstring` that start with **`-`**. I tried **`-`**, `-a`, `--a`, `-ab`, `-abc`, `-1`, `--1`, `-12`, `-123`, `--help` and `--version`. **`--`** did produce an error, but (2) if it’s an `expr` operator, it’s fewer than three characters, right? So the correct stdout is null. (OK, yes, it’s unfortunate that it gives an error message.) The `expr` answer is wrong for the general question, “last *n* characters” (it would fail for *n* = 1 or 2) but it seems to work for 3 and above.  … (Cont’d) – G-Man Says 'Reinstate Monica' Mar 28 '22 at 22:17
  • 1
    (Cont’d) … (3) And `expr "X$yourstring" : 'X.*\(...\)$'` would fix all (most?) of the problems, right?  (4) The question says “last *n* characters”, so it seems unfair to say that the answer would fail if the input isn’t characters.  (5) Also, I feel a little like you’re nit-picking on the `echo`. One could argue that the second answer is “provide your string as input to `rev | cut -n 1-3 | rev`”, and that gildux just made the tactical error of illustrating a bad way to pipe a string into a command. – G-Man Says 'Reinstate Monica' Mar 28 '22 at 22:17
  • @StéphaneChazelas POSIX implementation are compatible with old/initial behaviour from PWB: "The expr utility makes no lexical distinction between arguments which may be operators and arguments which may be operands. An operand which is lexically identical to an operator will be considered a syntax error." – gildux Mar 29 '22 at 20:45
  • @G-ManSays'ReinstateMonica' The question says "last three characters" not "last n characters" But I agree the input must be characters. Regarding use of `echo` that's because `rev` expect flow from stdin (when not a file) and it's a an easier way to go here in order to read the string characters. – gildux Mar 29 '22 at 20:58
  • @G-ManSays'ReinstateMonica', in the GNU implementation of `expr` (which you seem to be using given that version number which matches that of a recent GNU coreutils), `match`, `substr`, `index`, `length` are operators of more than 3 characters. In GNU `expr`, you can do `expr + "$yourstring" : '.*\(...\)$'` to work around the problem. busybox `expr` has `quote` in place of `+` (which makes it non-POSIX compliant). Arguments starting with `-` are a problem with at least FreeBSD's `expr` (unless `EXPR_COMPAT` is in the environment) – Stéphane Chazelas Mar 30 '22 at 06:59
  • @gildux: (6a) No, the question says “last three characters” ***and*** “the last *n* characters”. It would be common (and good practice) to interpret this as “*n;* for example, 3”; i.e., to write an answer that will work for any (non-negative integer) value of *n.* (6b) In case you missed it, Stéphane *correctly* points out that (unless you’re using some version of `expr` other than GNU) your answer fails for *n* = 3 when the input is `match`, `substr`, `index` or `length`. (7) What are you trying to tell me about `echo`? It seems almost as if you’re just repeating what I said (in my point #5). – G-Man Says 'Reinstate Monica' Mar 30 '22 at 17:19
  • @G-ManSays'ReinstateMonica', note that `match`, `substr`, `index`, `length` are not GNU-specific and are pretty common. `substr`, `index`, `length` were in the original implementation in PWB Unix in the late 70s. `match` was in SysIII in 1980. – Stéphane Chazelas Mar 30 '22 at 19:14
  • Sorry for not extrapolating the sentence "If I have a string with a variable number of characters, how can I print only the last three characters of the string." I didn't konw that "**only the last three**" must be understand as "n; for example, 3" and I'm puzzled. However, just use as many dots as _n_ value, no more complication. – gildux Mar 31 '22 at 19:17
1

Using Raku (formerly known as Perl_6)

The three main solutions employed here use either Raku's substr substring routine, or Raku's m/…/ match operator, or Raku's s/…/…/ substitution operator. Since Raku handles Unicode by default, many of the caveats listed within other answers to this question are obviated.

First three code examples return the last 3 characters per line (and a blank line if there are less than 3 characters available):

raku -ne 'put .substr( *-3 ) // "";' 

#OR

raku -ne 'put m{ .**3 $ } // "";'

#OR

raku -ne 'm{  .**3 $ } ?? $/.put !! "".put;' 
  1. Example 1 uses Raku's substr (substring) routine in conjunction with the // "defined-OR" operator. The substr routine takes a $from-to parameter, and *-3 indicates the third character from the end. Note, substr actually takes a Range here, so .substr(*-3,*) also works, with * representing the last (rightmost) character. This answer could also be written in sub form à la Perl5 as: raku -ne 'put substr($_, *-3) // "";'.

  2. The second answer uses Raku's m{ … } operator with user-defined delimiters (curly braces). The way to say three consecutive . characters of any kind is .**3. This is followed by the $ end-of-string zero-width assertion ($$ end-of-line zero-width assertion is also available in Raku). To handle the case where three characters are not available (and the match is therefore False/undefined), Raku's // "defined-OR" operator is employed to return an empty string.

  3. Example 3 uses the m{ … } match operator in conjunction with Raku's <Test> ?? <True> !! <False> ternary operator, to handle cases similar to the first example. The True condition returns $/.put wherein $/ represents Raku's match variable. Currently, $<> can be used interchangeably with $/, for those who have an aversion to slash/backslash characters.


Below, returning last 1 to 3 characters per line, depending on character availability (will return a blank line if given a blank line):

raku -ne 'put m[ .**{0..3} $ ];' 

#OR

raku -pe 's/ .*? (.**3) $ /$0/;'     

#OR

raku -pe 's/ <(.*?)> .**3 $ //;'

Answers 4-through-6 (above) return 0 up to 3 characters, depending on character availability:

  1. Again in example 4, the m[ … ] operator is employed with user defined delimiters (square braces). Here the match asks for .**{0..3} zero-to-three consecutive characters of any kind.
  2. In example 5, the s/…/…/ substitution operator is employed. The first atom .*? non-greedily ("frugally") matches any character zero-or-more times (the trailing ? indicates frugal and changes the default greedy to non-greedy). The second atom matches and captures (.**3) any character zero-or-more times (the parentheses direct capturing). In the replacement half, the $0 capture is returned. Because the first atom is non-greedy, the $0 capture varies in length from 0 up to 3 characters.
  3. Finally, in example 6 the s/…/…/ substitution operator is employed using a method that is somewhat opposite that of example 4. The same atoms as example 4 are employed, however this time the first atom <(.*?)> is captured. In fact, the <( … )> "pointy-curly" capture markers instruct Raku to drop anything outside of these markers. In the replacement half, this capture is deleted, leaving the .**3 $ second atom's characters.

Sample Input (first line is blank):

0
10
210
3210
43210
543210

Sample Output (code examples 1 through 3, three blank lines at top returned):

210
210
210
210

Sample Output (code examples 4 through 6, one blank line returned at top):

0
10
210
210
210
210

https://docs.raku.org/routine/substr
https://docs.raku.org/routine/substr-rw
https://raku.org

jubilatious1
  • 2,385
  • 8
  • 16
0

Get size of the string with:

size=${#STRING}

Then get substring of last n character:

echo ${STRING:size-n:size}

For example:

STRING=123456789
n=3
size=${#STRING}
echo ${STRING:size-n:size}

would give:

789
Esref
  • 553
  • 2
  • 6
  • 17
-1

printf will not work if string has spaces in it.

Below code for string with space

str="Welcome to Linux"
echo -n $str | tail -c 3

nux

Saurabh
  • 99
  • 2
  • 1
    Um, if `printf` doesn't work, then you are doing something _very_ wrong. – Kusalananda Mar 10 '18 at 10:42
  • 2
    @Kusalananda: Based on the command that Saurabh shows, they tried `printf $str` (rather than `printf "$str"` or ``printf '%s' "$str"``).  And, yes, `printf $str` is *very* wrong. (``echo -n $str`` isn’t much better.) – G-Man Says 'Reinstate Monica' May 24 '19 at 21:14
-1

tail -n 1 revisions.log | awk '{ print substr( $0, 0, length($0)-(length($0)-13 )) }'

If you want to print first thirteen characters from the begining

  • This is the same as [jasonwryan’s answer](https://unix.stackexchange.com/q/163481/80216#163483),  except adapted for a different question. – G-Man Says 'Reinstate Monica' Apr 02 '20 at 08:29
  • 1
    `length($0)-(length($0)-13)` is a very convoluted way to write `13`. That prints the first 13 characters of the last line of `revisions.log` which has nothing to do with what is being asked here. – Stéphane Chazelas Mar 28 '22 at 07:29