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.

  • Do you want the first three characters of the filename, or the contents of the files? – Sparhawk Feb 4 at 4:44
  • The contents of the files, edited it – William Chrétien Feb 4 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? – Jeff Schaller Feb 4 at 15:14
  • how far off is head -q -c 3 * ? – Jeff Schaller Feb 4 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 – William Chrétien Feb 4 at 15:16
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.

  • 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. – William Chrétien Feb 4 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 #!/? – Prvt_Yadv Feb 4 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 – William Chrétien Feb 4 at 14:10
  • @WilliamChrétien have you tried my edited command? – Prvt_Yadv Feb 4 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. – William Chrétien Feb 4 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.

  • 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 – William Chrétien Feb 4 at 12:32
  • The edited one seems to be a good solution, will try once I get home – William Chrétien Feb 4 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 ?

  • The shell did it when you asked it to expand ./holder/* – Jeff Schaller Feb 4 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 ? – William Chrétien Feb 4 at 23:52
  • Nope; ls will sort them (unless you tell it otherwise), and so will * – Jeff Schaller Feb 5 at 0:05
  • So the wildcard '*' sorted them before processing head on every files ? – William Chrétien Feb 5 at 0:08
  • That's correct. The bash shell does that, as do most shells. The zsh shell can sort wildcards in additional ways. – Jeff Schaller Feb 5 at 0:10

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.