0

I want to find all files in the current directory, sort them alphabetically before processing their content to extract the first 3 characters in each of them. Doing so will create a sentence that I can then redirect into another file. Ive try this :

find . -type f -exec basename {} \; | sort | xargs head -c 3 

But I get :

head: cannot open 'filenamehere' for reading: No such file or directory

for every files that im trying to extract their first 3 characters. I was being able to do it but without sorting it first.

7
  • Do you want the first three characters of the filename, or the contents of the files? Commented Feb 4, 2019 at 4:44
  • The contents of the files, edited it Commented Feb 4, 2019 at 15:10
  • why are you using find when you only want files in the current directory? Are there explicitly files in subdirectories that you also need to find and sort? How should they be sorted -- by their base filenames or by their whole path? Commented Feb 4, 2019 at 15:14
  • how far off is head -q -c 3 * ? Commented Feb 4, 2019 at 15:16
  • Theyre in a sub directory of the current one, their base filenames should be sorted before extracting the first 3 characters of each files Commented Feb 4, 2019 at 15:16

4 Answers 4

1

Edit: 1st:

find -type f -exec grep -I -q . {} \; -exec sh -c 'echo -e "$(basename "$0") {}"' {} \; | sort | cut -d ' ' -f2 | xargs sed -Ee 's/(^.{3})(.*)/\1/g' 

grep -I -q . {} \; will find all the text files only, not binary files like images,etc.`

2nd:

My basic idea was to make a hash function with key equals basename and it's value equals the first 3 characters.

#!/bin/bash touch file_s echo 'declare -A map' > file_s find . ! -name file_s ! -name sort_map -type f -exec grep -I -q . {} \; \ -exec sh -c 'i="$(basename "$0")";echo "map["$i"]=$(head -n 1 "$0"|cut -c 1-3)" >> file_s;' {} \; | sort | while read -r line do source file_s echo -e ${map["$line"]} done 

The name of the script where you should write this code is sort_map and file_s is a temp. file. So you should not include these two files in the find command . grep -I -q . {} \; will grep files which are only text, not binary files.

The second -exec command is as follows:

i="$(basename "$0")"; will get the basename and will write into variable i.

echo "map["$i"]=$(head -n 1 "$0"|cut -c 1-3)" >> file_s; will write hash function and it's value to temp file file_s.

sort will sort the file names.

while read -r line do source file_s echo -e ${map["$line"]} done 

Will read line by line and source the file file_s. Then will print the first 3 characters.


You can't use head because head prints the content of the file not the file names.

You can use:

find . -type f -exec basename {} \; | sort | cut -c 1-3 

Or you can also use b option instead of c but it will assume that all the characters are of 1 byte.

find . -type f -exec basename {} \; | sort | cut -b 1-3 

It will get first three characters.

You can use:

find . -type f -exec basename {} \; | sort | sed -Ee 's/(^.{3})(.*)/\1/g' 

It will sort the files and then will match starting three characters and will only print them.

Note: All of these commands will consider space and tab as one character.

12
  • Sorry I may have not been clear about head. I actually want it to output the first 3 characters of the content of every files found who was sorted. Commented Feb 4, 2019 at 12:31
  • @WilliamChrétien can you tell about your files. Are all files text files like endign with .txt. What if there is a shell script with starting #!/bin/bash do you want to print #!/? Commented Feb 4, 2019 at 12:55
  • Theyre all common names with no extension with their contents being gibberish except the 3 first characters in them. I need to sort them out alphabetically before extracting their first 3 characters in them, doing so will create a sentence that I need to > into a different file once I got it figured out Commented Feb 4, 2019 at 14:10
  • @WilliamChrétien have you tried my edited command? Commented Feb 4, 2019 at 14:13
  • Im afraid I cannot try this kind of command since I have not learned that yet and I dont fully understand it. My mission say that I should be able to get it done with find, sort and head. Commented Feb 4, 2019 at 14:18
0

Don't run basename on every file in find, that is a massive waste of resources, starting a shell for every file found.

find (assuming the GNU version, although other find versions may also support this) has a directive -printf that takes a format string which tells it how to output information about the file, including size, ownership, permissions, etc.; not just its name.

One of those formats is %f which is the filename with any leading directories removed, i.e. exactly what you are using basename for. So you can use

find -type f -printf '%f\n' 

You need to add the newline \n so that each filename is on each own line.

An additional detail that works in your favour is that you can add a limit on the number of characters to be printed in such a way. If you want at most 3 characters of the filename to be printed, use:

find -type f -printf '%.3f\n' 

Now just add sort to the mix:

find -type f -printf '%.3f\n' | sort 

and you have your solution.

EDIT: as it appears that the files need to be sorted, and then the first 3 characters of their contents need to be output, the command becomes:

find -type f -printf '%f\t%p\n' | sort | cut -f2 | xargs head -n 1 | cut -c1-3 

This uses the format string to first show just the base filename, followed by a tab, followed by the complete path. This can be used to easily sort the filenames.

cut -f2 extracts just the part after the tab.

xargs head -n 1 gets the first line of each file.

cut -c1-3 shows the first three characters of each line.

The head -n 1 part is needed in the pipeline, or else cut will show the first 3 characters of every line of each file.

2
  • Great response but sorry I may have not been clear about head. I actually want it to output the first 3 characters of the content of every files found who was sorted Commented Feb 4, 2019 at 12:32
  • The edited one seems to be a good solution, will try once I get home Commented Feb 4, 2019 at 14:54
0

Using GNU head (for -q) and the shell's default sorting of wildcard expansions:

head -q -c 3 * > /tmp/output 

Alternatively,

for file in *; do dd status=none if="$file" bs=1 count=3; done > /tmp/output 

If your files are in a single subdirectory (as per your separate comment), then simply adjust the wildcard:

head -q -c 3 /directory/subdirectory/* > /tmp/output 

and

for file in /directory/subdirectory/* # ... 
0

So ive finally got it down with my own solution inspired by answers. Ive had to do it in 3 commands rather than 1. I did :

mkdir holder find . -type f -exec mv -t holder {} \; head -qc 3 ./holder/* > prophetie.txt 

Which got the whole sentence from left to right just as it needed to be. When I

ls -l holder 

They were all alphabetically in order, didnt even need to sort them when doing so.. I guess the mv command did it ?

5
  • The shell did it when you asked it to expand ./holder/* Commented Feb 4, 2019 at 23:49
  • Ive just retried it and they got sort before the head command. How is that. Edit : Did you mean when I mv them to holder since it expanded it classed them alphabetically ? Commented Feb 4, 2019 at 23:52
  • Nope; ls will sort them (unless you tell it otherwise), and so will * Commented Feb 5, 2019 at 0:05
  • So the wildcard '*' sorted them before processing head on every files ? Commented Feb 5, 2019 at 0:08
  • That's correct. The bash shell does that, as do most shells. The zsh shell can sort wildcards in additional ways. Commented Feb 5, 2019 at 0:10

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.