35

I have a text file I'm outputting to a variable in my shell script. I only need the first 50 characters however.

I've tried using cat ${filename} cut -c1-50 but I'm getting far more than the first 50 characters? That may be due to cut looking for lines (not 100% sure), while this text file could be one long string-- it really depends.

Is there a utility out there I can pipe into to get the first X characters from a cat command?

  • 9
    You forgot a |? cat ${filename} | cut -c1-50 – DisplayName Nov 13 '14 at 18:28
  • @DisplayName fixed, thanks for catching my retyping error. – jkj2000 Nov 13 '14 at 18:52
  • 1
    @jkj2000, I have reverted back to the older version as that was the original question. – Ramesh Nov 14 '14 at 4:07
52
head -c 50 file

This returns the first 50 bytes.

Mind that the command is not always implemented the same on all OS. On Linux and macOS it behaves this way. On Solaris (11) you need to use the gnu version in /usr/gnu/bin/

  • head has no -c option. I’d go for dd(1) instead. – mirabilos Nov 14 '14 at 9:20
  • 6
    Note that this answer assumes that the file contains only ASCII characters, as the OP asked for the first X characters, not bytes. – Calimo Nov 14 '14 at 9:29
  • 2
    @mirabilos It might not be portable, but my version (GNU coreutils 5.97) does. – Yossarian Nov 14 '14 at 11:57
  • 1
    POSIX doesn't define -c as a valid option, however, so it is definitely dependent on your local environment. unix.com/man-page/posix/1/head – Jules Nov 14 '14 at 12:59
  • 1
    @Calimo Yes, I know, but I tried making a text file with 100 characters then running my command and it printed 50 characters. But you're right about ASCII, but since OP flagged this as answered there were none in his case. – DisplayName Nov 14 '14 at 13:28
26

Your cut command works if you use a pipe to pass data to it:

cat ${file} | cut -c1-50 

Or, avoiding a useless use of cat and making it a little safer:

cut -c1-50 < "$file"

Note that the commands above will print the first 50 characters (or bytes, depending on your cut implementation) of each input line. It should do what you expect if, as you say, your file is one huge line.

8
dd status=none bs=1 count=50 if=${filename}

This returns the first 50 bytes.

  • dd has no status=none flag. Use 2>/dev/null instead (and quote properly): dd if="$filename" bs=1 count=50 2>/dev/null (even so, consider using bs=50 count=1 to reduce the number of syscalls involved). – mirabilos Nov 14 '14 at 9:18
  • 1
    @mirabilos dd does have status=none when using Ubuntu 14.04, coreutils 8.21, but you're right to use 2>/dev/null if using a earlier version. – doneal24 Nov 14 '14 at 16:19
  • 1
    @mirabilos Most Linux distros use GNU coreutils as does FreeBSD and other BSDs. It is available on Solaris as package gnu-coreutils. Yes, this is "Unix & Linux" and both Unix and Linux systems use GNU coreutils. – doneal24 Nov 14 '14 at 18:02
  • 2
    No, Unix systems do not generally use GNU utilities. GNU is an acronym for “GNU is not Unix”, even. Please stick to portable solutions, or, if you must give GNU-only solutions, state so, and, if at all possible, show an equivalent portable solution. – mirabilos Nov 14 '14 at 19:23
  • 1
    Strictly speaking, that does one read() of 50 bytes. If the file is a pipe for instance and fewer characters are available at the time, then fewer bytes will be returned. To have the equivalent of head -c50, you'd need to use the GNU specific iflag=fullblock. – Stéphane Chazelas Nov 21 '14 at 10:29
4

Most answers so far assume that 1 byte = 1 character, which may not be the case if you are using a non-ASCII locale.

A slightly more robust way to do it:

testString=$(head -c 200 < "${filename}") &&
  printf '%s\n' "${testString:0:50}"

Note that this assumes:

  1. You are using ksh93, bash (or a recent zsh or mksh (though the only multi-byte charset supported by mksh is UTF-8 and only after set -o utf8-mode)) and a version of head that supports -c (most do nowadays, but not strictly standard).
  2. The current locale is set to the same encoding as the file (type locale charmap and file -- "$filename" to check that); if not, set it with ie. LC_ALL=en_US.UTF-8)
  3. I took the first 200 bytes of the file with head, assuming the worst-case UTF-8 where all the characters are encoded on at most 4 bytes. This should cover most cases I can think of.
  • Of course, this also assumes GNU head, or another implementation of it which adds the nōn-standard -c option. But you’re requiring GNU bash already. (Note: mksh’s UTF-8 mode could do this for UTF-8 encoded files.) I’d ask the OP if they require octets or multibyte characters, just “characters” is a vague/gerneric term. – mirabilos Nov 14 '14 at 15:02
  • That also assumes $filename or $testString doesn't contain blank newline or wildcards or start with -. – Stéphane Chazelas Nov 21 '14 at 10:32
  • The ${var:offset:length} construct you're using here actually comes from ksh93 and is also supported by recent versions of zsh (zsh has its own $testString[1,50]). You need ${testString:0:50} in ksh93 and zsh however. – Stéphane Chazelas Nov 21 '14 at 10:35
  • Just edited my answer to address the comments above – Calimo Apr 6 '17 at 15:15
2
grep -om1 "^.\{50\}" ${filename}

Other variant (for first line in file)

(IFS= read -r line <${filename}; echo ${line:0:50})
  • This is abuse of high-level tools – and prone to not doing what you want, e.g. if they’re locale-aware. – mirabilos Nov 14 '14 at 9:19
  • @mirabilos What do you mean under high-level tools: read and echo ? Or bash expansion ? – Costas Nov 14 '14 at 10:06
  • grep (regexp), and yes, the use of shell here (hint: the first line may be large). (That being said, the bashism is also not in POSIX, but most shells implement that.) – mirabilos Nov 14 '14 at 11:51
-2

You can use sed for this which will tackle the problem pretty easily

sed -e 's/^\(.\{50\}\).*/\1/' yourfile
  • Curious to know how this got downvoted if it solves the OP's question: "I only need the first 50 characters" This accomplishes what was requested without UUOC (Useless Use of Cat) – munkeyoto Nov 14 '14 at 15:28
  • 1
    This answer gives the first fifty characters of each line in the file, not just the first 50 of the file. Also doesn't print anything at all if all the lines are less than 50 characters long. Your solution would work better with sed -n -e '1s/^\(.\{50\}\).*/\1/p' ${filename} – doneal24 Nov 14 '14 at 16:22
  • Understood could have just: head -n 1 | sed -e 's/^(.\{50\}).*/\1/' ... And it would have solved the issue. OP stated: "only need the first 50 characters" – munkeyoto Nov 14 '14 at 16:42
  • 1
    Nope. If the first line is only 49 characters long it would output nothing. – doneal24 Nov 14 '14 at 18:03
  • Doug I understood this the first time around yet the OP mentioned nothing about printing if the line contained less than 50 chars, so I still fail to see your point, nor the point of this being downvoted since again it fell into what would have worked with head: head -n 1 ${filename} | sed -n -e '1s/^(.\{50\}).*/\1/p' – munkeyoto Nov 14 '14 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.