This one will require some knowledge of both bash and tcsh. I would like to put a line in a bash script that calls a command with some arguments, but using the tcsh interpreter. Suppose the command I want to call is ls (which is silly, but it demonstrates the problem). So if my_script contains:
#!/bin/bash
/bin/tcsh -c "ls"' "$*"' "${#}"
and then I call it as follows:
my_script "first file" "second file"
I get this:
ls: first file second file: No such file or directory
The problem is that tcsh is receiving only one argument, first file second file, instead of two arguments. How can I remedy this?
Despite the comments, there's no direct problem with tcsh (and believe me, I'm no fan of C shell), nor is there a problem with bash per se. The problems would be similar, in fact, if you replaced the tcsh with bash.
The problem is that what you're trying to do is actually extremely difficult to do. Let me explain...
In the bash script, you are trying to create a single string that will contain a valid command line that tcsh will interpret correctly, including preserving spaces in arguments.
Developing an answer step-by-step
Let's start with some easy stuff — arguments without spaces in them:
set -- /bin/ls /bin/sh /bin/bash # Set the arguments to bash
/bin/tcsh -c "ls -l $*"
This will work fine; it will execute the C shell and the C shell will process the string and execute:
ls -l /bin/ls /bin/sh /bin/bash
So, the problem is how to relay arguments with spaces in them to C shell reliably when the command as a whole is being specified as a single string.
You already know that this runs into problems:
mkdir "./a b c" "./d e f"
set -- "a b c" "d e f" # Two arguments with spaces
/bin/tcsh -c "ls -al $*"
On my machine, I get:
ls: a: No such file or directory
ls: b: No such file or directory
ls: c: No such file or directory
ls: d: No such file or directory
ls: e: No such file or directory
ls: f: No such file or directory
If we do the expansion manually, we can get the desired result (for this limited example) with:
mkdir "./a b c" "./d e f"
set -- "a b c" "d e f" # Two arguments with spaces
/bin/tcsh -c "ls -al 'a b c' 'd e f'"
This yields:
a b c:
total 0
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
drwxr-xr-x 4 jleffler staff 136 Aug 25 12:21 ..
d e f:
total 0
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
drwxr-xr-x 4 jleffler staff 136 Aug 25 12:21 ..
(I'm going to assume that the two directories 'a b c' and 'd e f' exist from here onwards without creating them each time.)
So, the objective must be to find a way to create a string that will be safe when interpreted by the C shell, automatically (not manually as shown). Because of the metasyntactic zoo that C shell has (lots of special characters), the full task will be hard, but let's get the easy stuff done first — spaces and no metacharacters.
For each argument, we want to add single quotes to the start and end, and ensure that any single quotes inside the string are protected. That is its own little party; the trick is to replace the embedded single quotes with the sequence '\'' where the first single quote ends the current single-quoted string, the backslash single-quote embeds a single quote, and the final single quote starts a new single-quoted string. And we want that added to the end of the current command string. So, this leads to:
set -- "a b c" "d e f" # Two arguments with spaces
cmd="ls -al"
for arg in "$#"
do escaped=$(sed -e "s/'/'\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
cmd="$cmd $escaped"
done
echo "$cmd"
tcsh -c "$cmd"
This yields (the ls line is from the echo, of course):
ls -al 'a b c' 'd e f'
a b c:
total 0
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
drwxr-xr-x 4 jleffler staff 136 Aug 25 12:21 ..
d e f:
total 0
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
drwxr-xr-x 4 jleffler staff 136 Aug 25 12:21 ..
OK, so far, so good. What about the metasyntactic zoo? Fortunately, most of the characters have no special meaning inside single quotes.
Time to add some more complex directories to the list (these will survive for the duration of the question, too). Make sure you know what names are being created; you need to understand shell quoting rather well.
As an exercise, for each directory name created during this question, write alternatives that give the same result when enclosed in single quotes, when enclosed in double quotes, and without any quotes around the whole argument.
$ mkdir '! % *' '$(pwd)' '`pwd`'
And the script is mostly unchanged — it uses the shell glob to generate the list of directory names, echoes each argument in turn, and lists the inode numbers too:
set -- *
cmd="ls -ail"
for arg in "$#"
do echo "arg: $arg"
escaped=$(sed -e "s/'/'\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
cmd="$cmd $escaped"
done
echo "cmd: $cmd"
tcsh -c "$cmd"
Hey presto:
arg: ! % *
arg: $(pwd)
arg: `pwd`
arg: a b c
arg: d e f
cmd: ls -ail '! % *' '$(pwd)' '`pwd`' 'a b c' 'd e f'
! % *:
total 0
1640119 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 .
1640040 drwxr-xr-x 7 jleffler staff 238 Aug 25 12:34 ..
$(pwd):
total 0
1640120 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 .
1640040 drwxr-xr-x 7 jleffler staff 238 Aug 25 12:34 ..
`pwd`:
total 0
1640121 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 .
1640040 drwxr-xr-x 7 jleffler staff 238 Aug 25 12:34 ..
a b c:
total 0
1640056 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
1640040 drwxr-xr-x 7 jleffler staff 238 Aug 25 12:34 ..
d e f:
total 0
1640057 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
1640040 drwxr-xr-x 7 jleffler staff 238 Aug 25 12:34 ..
Just what the doctor ordered! But we haven't yet been brutal enough: like Knuth says, you have to get into a really nasty mean mindset when you're testing code, so let's try:
$ mkdir "O'Reilly's Books"
$ mkdir "' \` \""
$ mkdir '${HOME}' '$PATH' 'He said, "Don'\''t Do It!"'
$ ls -l
total 0
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 ! % *
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 $(pwd)
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 $PATH
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 ${HOME}
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 ' ` "
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 He said, "Don't Do It!"
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 O'Reilly's Books
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 `pwd`
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 a b c
drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 d e f
$
And the result is:
arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a b c
arg: d e f
cmd: ls -ail '! % *' '$(pwd)' '$PATH' '${HOME}' '''' ` "' 'He said, "Don'''t Do It!"' 'O'''Reilly'''s Books' '`pwd`' 'a b c' 'd e f'
Unmatched `.
That's not what we wanted. Part of the trouble, though, is that sequence of 4 single quotes in the line tagged 'cmd:'; it should be ''\''. So, the sed script isn't accurate enough.
set -- *
cmd="ls -ail"
for arg in "$#"
do echo "arg: $arg"
escaped=$(sed -e "s/'/'\\\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
cmd="$cmd $escaped"
done
echo "cmd: $cmd"
tcsh -c "$cmd"
And when it is run, we get:
arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a b c
arg: d e f
arg: x.sh
cmd: ls -ail '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a b c' 'd e f' 'x.sh'
1640231 -rw-r--r-- 1 jleffler staff 223 Aug 25 12:56 x.sh
! % *:
total 0
1640119 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
$(pwd):
total 0
1640120 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
$PATH:
total 0
1640176 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
${HOME}:
total 0
1640175 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
' ` ":
total 0
1640163 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
He said, "Don't Do It!":
total 0
1640177 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
O'Reilly's Books:
total 0
1640164 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
`pwd`:
total 0
1640121 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
a b c:
total 0
1640056 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
d e f:
total 0
1640057 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 .
1640040 drwxr-xr-x 13 jleffler staff 442 Aug 25 12:56 ..
Mean enough? Getting close. What about directory names containing backslashes?
$ mkdir "a \\' \\\` \\$ b \\\" c" # Make sure you do the exercise!
$ mkdir 'a \\'\'' \\\` \\$ b \\\" c' # Make sure you do the exercise!
$ ls -li
total 8
1640119 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 `pwd`
1640243 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:58 a \' \` \$ b \" c
1640259 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:01 a \\' \\\` \\$ b \\\" c
1640056 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 a b c
1640057 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 d e f
1640231 -rw-r--r-- 1 jleffler staff 223 Aug 25 12:56 x.sh
$
And with the ls -ail changed to ls -dil, the output is:
$ bash x.sh
arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a \' \` \$ b \" c
arg: a \\' \\\` \\$ b \\\" c
arg: a b c
arg: d e f
arg: x.sh
cmd: ls -dil '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a \'\'' \` \$ b \" c' 'a \\'\'' \\\` \\$ b \\\" c' 'a b c' 'd e f' 'x.sh'
1640119 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 `pwd`
1640243 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:58 a \' \` \$ b \" c
1640259 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:01 a \\' \\\` \\$ b \\\" c
1640056 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 a b c
1640057 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 d e f
1640271 -rw-r--r-- 1 jleffler staff 223 Aug 25 13:03 x.sh
$
Working Script
set -- *
cmd="ls -ail"
for arg in "$#"
do echo "arg: $arg"
escaped=$(sed -e "s/'/'\\\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
cmd="$cmd $escaped"
done
echo "cmd: $cmd"
tcsh -c "$cmd"
Summary
The key parts to the solution are:
Recognizing that single quotes are needed around the arguments.
Knowing how to escape single quotes.
Knowing how to escape backslashes.
Being really brutal when you do your testing!
It helps if you've done it before...
Oh futz! I forgot to test arguments containing newlines:
$ mkdir "a
> b
> c"
$ ls -li
total 8
1640119 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 `pwd`
1640336 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:16 a?b?c
1640243 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:58 a \' \` \$ b \" c
1640259 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:01 a \\' \\\` \\$ b \\\" c
1640056 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 a b c
1640057 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:21 d e f
1640271 -rw-r--r-- 1 jleffler staff 223 Aug 25 13:03 x.sh
$
Well, there are some reasons why you should never try to parse the output from ls; it has generated question marks in place of the newlines (this is on Mac OS X 10.8.1 and is not GNU ls, just for those keeping score at home; other systems may behave differently).
And when the script (x.sh) is run, I get:
$ bash x.sh
arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a
b
c
arg: a \' \` \$ b \" c
arg: a \\' \\\` \\$ b \\\" c
arg: a b c
arg: d e f
arg: x.sh
cmd: ls -dil '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a'
'b'
'c' 'a \'\'' \` \$ b \" c' 'a \\'\'' \\\` \\$ b \\\" c' 'a b c' 'd e f' 'x.sh'
ls: a: No such file or directory
1640119 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x 2 jleffler staff 68 Aug 25 12:34 `pwd`
b: Command not found.
c: Command not found.
$
There are multiple issues here. The sed script treated each line of the argument separately. That really isn't soluble using sed; or, perhaps more accurately, it isn't something I want to solve using sed. Æons ago, I wrote a C program escape to do the job that the sed script almost does.
#!/bin/bash
set -- *
escaped=$(escape "$#")
cmd="ls -dil $escaped"
echo "cmd: $cmd"
bash -c "$cmd"
tcsh -c "$cmd"
Note that I've added an invocation of bash in there. The output is:
cmd: ls -dil '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a
b
c' 'a b c' 'd e f' x.sh
178474064 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 ! % *
178474065 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 $(pwd)
178474219 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 $PATH
178474218 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 ${HOME}
178474170 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 ' ` "
178474220 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 He said, "Don't Do It!"
178474131 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 O'Reilly's Books
178474066 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 `pwd`
178474998 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:40 a?b?c
178473958 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 a b c
178473959 drwxr-xr-x 2 jleffler staff 68 Aug 25 13:38 d e f
178475097 -rw-r--r-- 1 jleffler staff 115 Aug 25 13:41 x.sh
Unmatched '.
b: Command not found.
Unmatched '.
Wassup? Well, bash and other shells derived from the Bourne shell such as ksh, are OK with a string starting on one line and continuing over other lines, but the C shell and its derivatives are not. They demand a backslash before the newline. So, to work with tcsh, I'd have to upgrade escape to generate the output for the C shell. Not at all hard to do, but it would need doing. Presumably, that would be an option -c and for general purpose safety, the invocation would become:
escaped=$(escape -c -- "$#")
with the double-dash preventing misinterpretation of arguments in "$#" as options to escape itself. In part, this goes to show that it is hard to write scripts that deal with file names that contain characters outside the portable file name character set. Fortunately, I don't have to deal with the C shell very often; I don't plan to make that a part of escape because it is a change of interface (the current code does not have any options of its own, so I do not use the double-dash notation with escape). If I need it, it will become cescape to unconditionally support the C shell.
Your tcsh script is wrong. Beware, I don't know tcsh, but this is what I can tell from a few tests.
change it to
#!/bin/bash
/bin/tcsh -c 'ls "$1" "$2"' "$#"
You would need a "$#" instead of your "$*" on the tcsh side just like on the bash side. In bash, "$*" expands to a single word (the concatenation of all positional arguments, separated by the first IFS character (default space)), whereas "$#" expands to one word for each positional argument.
In tcsh, it seems like "$*" does the same, but I found no equivalent of "$#". In this case, you know the number of arguments and can use "$1" "$2" instead. Note that many people have cursed tcsh, so it might be very possible that there simply is no better solution...
So, to correct your assumptions, in your original script, it is not tcsh which receives only a single argument from bash, but it's ls receiving only a single one from tcsh.
To answer my own question... this is the solution:
/bin/tcsh -c "ls "'$argv:q' "${#}"
Related
Is there any way to take away the owner's permission to read a file in macOS? I know there's no reason to do this but I have to for school and I can't find an answer anywhere. Removing my write permission works fine but when I try to remove my read permission it automatically give me my read and write permissions back. As you can see in the console when I use chmod -v -v (extra verbose) it shows the correct permissions it should be changed to but then when checking afterwards they havent changed into that...
thijs#Thijss-MacBook-Air-2 week6 % ls -l
total 16
-rw----r-- 1 thijs staff 12 Oct 11 21:10 greeting.txt
-rw-r--r-- 1 thijs staff 0 Oct 11 21:10 hello.txt
-rw------- 1 thijs staff 15 Oct 11 21:11 weather.txt
thijs#Thijss-MacBook-Air-2 week6 % chmod -v -v u-w weather.txt
weather.txt: 0100600 [-rw------- ] -> 0100400 [-r-------- ]
thijs#Thijss-MacBook-Air-2 week6 % ls -l
total 16
-rw----r-- 1 thijs staff 12 Oct 11 21:10 greeting.txt
-rw-r--r-- 1 thijs staff 0 Oct 11 21:10 hello.txt
-r-------- 1 thijs staff 15 Oct 11 21:11 weather.txt
thijs#Thijss-MacBook-Air-2 week6 % chmod -v -v u-r weather.txt
weather.txt: 0100400 [-r-------- ] -> 0100000 [---------- ]
thijs#Thijss-MacBook-Air-2 week6 % ls -l
total 16
-rw----r-- 1 thijs staff 12 Oct 11 21:10 greeting.txt
-rw-r--r-- 1 thijs staff 0 Oct 11 21:10 hello.txt
-rw------- 1 thijs staff 15 Oct 11 21:11 weather.txt
I am currently sorting the output of ls -l by byte count using:
ls -l | sort -r -k5,5 -n
What if I wanted to make this work with the -# flag? Currently this will output:
-rwxr-xr-x# 1 name staff 7106 2 May 10:43 c
-rwxr-xr-x 1 name staff 675 22 Apr 17:57 a
-rwxr-xr-x 1 name staff 486 23 Apr 07:56 b
drwxr-xr-x 4 name staff 136 25 Apr 18:38 d
-rwxr-xr-x 1 name staff 120 23 Apr 07:59 e
-rwxr-xr-x 1 name staff 112 22 Apr 18:45 g
-rwxr-xr-x 1 name staff 51 22 Apr 18:45 f
total 56
com.apple.metadata:_kMDItemUserTags 42
Where I want it to take the extended attribute keys line and keep it below the appropriate file like so:
-rwxr-xr-x# 1 name staff 7106 2 May 10:43 c
com.apple.metadata:_kMDItemUserTags 42
-rwxr-xr-x 1 name staff 675 22 Apr 17:57 a
-rwxr-xr-x 1 name staff 486 23 Apr 07:56 b
drwxr-xr-x 4 name staff 136 25 Apr 18:38 d
-rwxr-xr-x 1 name staff 120 23 Apr 07:59 e
-rwxr-xr-x 1 name staff 112 22 Apr 18:45 g
-rwxr-xr-x 1 name staff 51 22 Apr 18:45 f
total 56
No need to use sort, just use the -S option with ls
ls -Sl
(that's an upper case S)
I am experimenting with TCL command exec in tclsh and here are my results:
% set show_me_dir "ls"
ls
% exec $show_me_dir
VboxSharedFolder
% set show_me_dir "ls -la"
ls -la
% exec $show_me_dir
couldn't execute "ls -la": no such file or directory
% set show_me_dir {ls -la}
ls -la
% exec $show_me_dir
couldn't execute "ls -la": no such file or directory
% ls -la
total 141
d---------+ 1 wakatana Domain Users 0 Jan 22 19:12 .
d---------+ 1 wakatana Domain Users 0 Apr 16 2014 ..
----------+ 1 wakatana Domain Users 20214 Jan 23 18:43 .bash_history
----------+ 1 wakatana Domain Users 1494 Apr 15 2014 .bash_profile
----------+ 1 wakatana Domain Users 7593 Jan 22 19:03 .bashrc
d---------+ 1 wakatana Domain Users 0 Jan 15 14:56 VboxSharedFolder
%
Can somebody please explain how can I execute command with arguments?
Edit:
The following example from Expanding a list of parameters in Tcl and eval article was big eye opener of what is going on here:
The variable $action is only expanded into the string "piemiddle apple" AFTER the command line has been split into its individual parameters:
% set action {piemiddle apple}
% set $action
can't read "piemiddle apple": no such variable
Result: set command "sees" one argument, equivalent to:
% set {piemiddle apple}
The expand operator allows you to specify that a variable is to be expanded BEFORE the command line is split into individual parameters:
% set action {piemiddle apple}
% set {*}$action
apple
Result: set command "sees" two arguments, equivalent to:
% set piemiddle apple
In earlier versions of Tcl, the eval command was the recommended alternative and it remains available today.
% set action {piemiddle apple}
% eval set $action
apple
Another examples which proves functionality of expansion operator:
% set {*}"name Linus"
Linus
% puts $name
Linus
%
%
% set distro Unbuntu
Unbuntu
% set {*}"linux $distro"
Unbuntu
% puts $linux
Unbuntu
%
%
Finally the discovery that exec needs command as it's first argument and first command option as it's second argument etc.
% exec "ls" "-la"
total 137
d---------+ 1 wakatana Domain Users 0 Jan 22 19:12 .
d---------+ 1 wakatana Domain Users 0 Apr 16 2014 ..
----------+ 1 wakatana Domain Users 20214 Jan 23 18:43 .bash_history
----------+ 1 wakatana Domain Users 1494 Apr 15 2014 .bash_profile
----------+ 1 wakatana Domain Users 7593 Jan 22 19:03 .bashrc
d---------+ 1 wakatana Domain Users 0 Jan 15 14:56 VboxSharedFolder
%
%
% exec "ls -la"
couldn't execute "ls -la": no such file or directory
The safest way to build a command for exec is to use Tcl's list. For example:
% set tcl_version
8.5
% set cmd [list ls -l tmp]
ls -l tmp
% eval exec $cmd
total 32
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 file.txt
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 foo-1.dat
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 foo-2.dat
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 foo-3.dat
% exec {*}$cmd
total 32
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 file.txt
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 foo-1.dat
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 foo-2.dat
-rw-r--r-- 1 pynexj staff 1176 Jan 23 23:24 foo-3.dat
%
Note that {*} is a new syntax of Tcl 8.5 which can help reduce the uses of eval.
As example for ls command you can do:
exec {*}ls -lsa {*}[glob *.cpp]
Please have a look at What does {*} do in TCL?
How would you go about changing permissions for a file or in a directory recursively in such a way that group permissions would be copied over to world permissions, with no other changes? For example, to go from this directory listing:
drwxr-x--- 2 septi septi 4096 Jun 29 01:14 example.d
-rw-r----- 1 septi septi 0 Jun 29 01:14 example.r
-rwxr-x--- 1 septi septi 0 Jun 29 01:14 example.x
...to:
drwxr-xr-x 2 septi septi 4096 Jun 29 01:14 example.d
-rw-r--r-- 1 septi septi 0 Jun 29 01:14 example.r
-rwxr-xr-x 1 septi septi 0 Jun 29 01:14 example.x
From the chmod(1) man page (relevant parts extracted):
-R Change the modes of the file hierarchies rooted in the files
instead of just the files themselves.
And:
The symbolic mode is described by the following grammar:
who ::= a | u | g | o
op ::= + | - | =
perm ::= r | s | t | w | x | X | u | g | o
The who symbols "u", "g", and "o" specify the user, group, and
other parts of the mode bits, respectively. The who symbol a is
equivalent to ugo.
The perm symbols represent the portions of the mode bits as follows:
g The group permission bits in the original mode of the file.
So for you:
chmod -R o=g *
Example:
$ ls -l
total 0
drwxr-x--- 2 carl staff 68 Jun 28 10:25 example.d
-rw-r----- 1 carl staff 0 Jun 28 10:25 example.r
-rwxr-x--- 1 carl staff 0 Jun 28 10:25 example.x
$ chmod -R o=g *
$ ls -l
total 0
drwxr-xr-x 2 carl staff 68 Jun 28 10:25 example.d
-rw-r--r-- 1 carl staff 0 Jun 28 10:25 example.r
-rwxr-xr-x 1 carl staff 0 Jun 28 10:25 example.x
Using only grep and sed, is there a way I can tranform the output of ls -l * into this :
-rw-r--r-- agenda.txt
-rw-r--r-- annuaire.txt
Thanks!
seeing that you have already got your "answer", here's one of the simpler solution
ls -l | tr -s " "| cut -d" " -f1,8-
#OP, sed is "powerful", but sometimes, simplicity is more powerful.
Side note: Don't parse file names like that.
ls -l | sed 's/[ ]+//g' | sed 's/ [0-9].*:.[0-9]/ /g'
ls -altrh| sed -E 's/ +.+ / / g'
Or you can go with ssed which supports Perl Regular Expressions.
I solved your problem using the ssed program you can install it in any Posix system, ssed stands for super sed.
so i did a ls -latrh in my home directory.
telsa:~ mahmoh$ ls -altrh
total 136
drwxr-xr-x 5 root admin 170B Jun 24 00:27 ../
drwx------+ 4 mahmoh staff 136B Jun 24 00:27 Pictures/
drwx------+ 3 mahmoh staff 102B Jun 24 00:27 Music/
drwx------+ 3 mahmoh staff 102B Jun 24 00:27 Movies/
drwx------+ 3 mahmoh staff 102B Jun 24 00:27 Desktop/
-rw------- 1 mahmoh staff 3B Jun 24 00:27 .CFUserTextEncoding
drwxr-xr-x+ 5 mahmoh staff 170B Jun 24 00:27 Public/
drwx------+ 5 mahmoh staff 170B Jun 24 02:19 Documents/
-rw-r--r--# 1 mahmoh staff 15K Jun 24 02:19 .DS_Store
drwx------# 36 mahmoh staff 1.2K Jun 24 14:48 Library/
-rw-r--r-- 1 mahmoh staff 279B Jun 24 15:27 .profile~
-rw-r--r--# 1 mahmoh staff 14K Jun 24 15:29 .vimrc
-rw-r--r-- 1 mahmoh staff 279B Jun 24 15:30 .profile
drwx------ 2 mahmoh staff 68B Jun 24 15:46 .Trash/
drwxr-xr-x 3 mahmoh staff 102B Jun 24 20:26 .mplayer/
-rw------- 1 mahmoh staff 3.5K Jun 24 22:11 .bash_history
-rw------- 1 mahmoh staff 42B Jun 24 23:25 .lesshst
-rw-r--r-- 1 mahmoh staff 3.6K Jun 24 23:39 temp
-rw-r--r-- 1 mahmoh staff 3.3K Jun 24 23:43 rtorrent.rc~
drwxr-xr-x 5 mahmoh staff 170B Jun 24 23:52 torrents/
-rw-r--r-- 1 mahmoh staff 3.3K Jun 24 23:56 .rtorrent.rc~
-rw------- 1 mahmoh staff 3.7K Jun 24 23:56 .viminfo
-rw-r--r-- 1 mahmoh staff 3.3K Jun 24 23:56 .rtorrent.rc
drwxr-xr-x+ 25 mahmoh staff 850B Jun 24 23:56 ./
drwx------+ 10 mahmoh staff 340B Jun 24 23:58 Downloads/
Now watch.
telsa:~ mahmoh$ ls -altrh| ssed -R -e 's/ +.+ / / g'
total 136
drwxr-xr-x ../
drwx------+ Pictures/
drwx------+ Music/
drwx------+ Movies/
drwx------+ Desktop/
-rw------- .CFUserTextEncoding
drwxr-xr-x+ Public/
drwx------+ Documents/
-rw-r--r--# .DS_Store
drwx------# Library/
-rw-r--r-- .profile~
-rw-r--r--# .vimrc
-rw-r--r-- .profile
drwx------ .Trash/
drwxr-xr-x .mplayer/
-rw------- .bash_history
-rw------- .lesshst
-rw-r--r-- temp
-rw-r--r-- rtorrent.rc~
drwxr-xr-x torrents/
-rw-r--r-- .rtorrent.rc~
-rw------- .viminfo
-rw-r--r-- .rtorrent.rc
drwxr-xr-x+ ./
drwx------+ Downloads/
ls -l | sed 's/^\([^\t ]\+\)\(.*:.[^ \t]\+\)\(.\+\)/\1 \3/'
Here is a working command. The slightly tricky thing is that ls -l will print the year for files that are older than some time (6 months) and hh:mm for newer files.
ls -l | sed 's/ .*[0-9]* .*[A-Z][a-z][a-z] [ 0-9][0-9] \{1,2\}[0-9][0-9]:*[0-9][0-9] / /'
For the following example
drwxr-xr-x 39 root root 1024 Feb 19 08:58 /
the starting .* will match 39 root root 1024 and then the rest of the regular expression matches month name (so you might restrict a-z to fewer characters) followed by year or hh:mm.
why not use awk instead of sed? awk is built for stuff like this.
see this manual page for more about fields in awk.
Like this?
ls -l | sed 's/ [0-9].*:.[0-9] / /' | less
Transforms
-rw-r--r-- 1 tomislav tomislav 609 2009-11-26 10:32 Test.class
-rw-r--r-- 1 tomislav tomislav 46 2009-12-14 12:16 test.groovy
into
-rw-r--r-- Test.class
-rw-r--r-- test.groovy