LA SHELL sh(1) ksh(1) sh-bourne(1) bash(1) ========================================== Existen varias shells para UNIX, Korn-Shell, Bourne-Shell, C-Shell, Key-Shell, Posix-Shell, Restricted-Shell,..etc. Nosotros vamos a tratar la Bourne Shell que es la mas estandar. La Korn-Shell es un superconjunto de la Bourne Shell y por ello todo lo que se diga para la Bourne-Shell (sh) vale casi seguro para la Korn-Shell (ksh). La Shell es un programa normal y corriente, pero incorpora muchos de los conceptos mas practicos de UNIX. No tiene nada de particular que algunos sistemas incorporen algunas otras shells. Estructura de la linea de orden: ------------------------------- Cuando aparece el prompt del sistema ($PS1) indica que la shell está esperando la introduccion de una orden. Las ordenes se terminan mediante . Si despues de pulsar el shell no considera que el comando este completo quedara esperando mas entrada mostrando el segundo introducto $PS2. Se puede escapar el caracter poniendo delante la barra '\'. Una orden constara de un numero variable de argumentos separados por blancos, por tab, o por \. En una orden se pueden distinguir comandos, opciones, argumentos, meta-caracteres, comentarios, comandos internos...etc. (Ojo en UNIX las mayusculas y minusculas son significativas.) Comandos: Son ficheros ejecutables. Para que la shell localice el comando debera estar en un subdirectorio que forme parte de la variable PATH o de lo contrario debe especificarse el camino completo. Opciones: Generalmente las opciones de un comando son letras precedidas de un signo '-'. En algunos comandos se pueden poner varias opciones como varias letras seguidas precedidas del signo '-'. En algunos comandos el orden de las opciones es muy significativo. Meta-caracteres (Uno de los siguientes caracteres) : ; & ( ) | < > argumentos: Son literales tomados como parametro de entrada para algun comando. comentarios: Todo lo que sigue al caracter '#' hasta sera un comentario. Comandos internos: Comandos que estan implementados dentro de la propia shell. No necesitan PATH. Ejemplos de comandos internos son: cd, exec, arg, eval, exit,... Ejemplos: # Orden con un unico comando $PS1> ps # Orden con un comando y una opcion $PS1> ps -l # Orden con un comando y tres opciones. $PS1> ps -elf # Forma equivalente de la anterior. $PS1> ps -e -l -f # La siguiente orden era para listar los procesos del usuario # dacs pero no funciona correctamente. $PS1> ps -ul dacs # El orden de las opciones ahora si es correcto. Porque despues # de la opcion -u tiene que seguir el nombre de usuario. $PS1> ps -lu dacs # Otra forma equivalente seria. Comado opcion 'u' argumento 'dacs' # y opcion 'l'. $PS1> ps -u dacs -l Expansion de la linea de orden: ------------------------------- Antes de que la shell ejecute comando alguno expande la linea de ordenes. La orden resultante puede ser muy larga y tener muchos argumentos. Por ello los comandos toman los argumentos que les pasa la shell despues de la expansion. El '*' se expande en base a los nombres de ficheros presentes en nuestro directorio actual, sustituyendose por una cadena de caracteres cualquiera. Si en nuestro subdirectorio tenemos los ficheros siguientes: kk1 kk2 kkkk kk.txt kk.doc j2.txt * --> kk1 kk2 kkkk kk.txt kk.doc j2.txt k* --> kk1 kk2 kkkk kk.txt kk.dox *xt --> kk.txt j2.txt *. --> *.* --> kk.txt kk.doc j2.txt El '?' se expande como un unico caracter. En el ejemplo anterior: ??? --> kk1 kk2 kk? --> kk1 kk2 ............................................................. Problema: Explicar que ocurre con las ordenes siguientes: $PS1> ls | wc 6487 6487 84282 $PS1> ls * ksh: /bin/ls: arg list too long ............................................................. Operador grave: --------------- Cuando colocamos algo dentro de las comillas graves '`' la shell lo interpretara como una cadena cuyo valor es sustituido por el resultado de ese comando que produce ese comando en salida estandar. Caracteres de escape: --------------------- Dado que la Shell interpreta catarteres. Blancos como separadores. Asteriscos e interrogaciones como comodines. Operador grave. Comillas dobles, Comillas normales, $, etc...Existen formas de escapar carac- teres para que la shell no los expanda, o interprete. La shell no el el unico programa con capacidad de expandir e inter- pretar caracteres especiales. Por ejemplo find, egrep, sed, y otros tambien interpretan ciertos caracteres que ademas pueden coincidir con algunos de los que interpreta la shell. Para usar caracteres especiales que pueden interpretarse por la shell habra que escaparlos siempre que deseemos que llegen al comando. 1) Cuando usamos las dobles comillas, los comodines asterisco, interrogación y caracteres en blanco no se interpretan, aunque si se interpretan '$', comillas graves '`', y otros. 2) Cuando usamos comillas normales se respeta todo el contenido de la cadena sin expandir nada. 3) Cuando usamos el caracter '\' escapamos el caracter que va inme- diatamente despues. Cuando es un caracter que tiene un significado especial para la shell lo convierte en un caracter normal. Ejemplos: # Consultar en el manual el comando echo. $PS1> man 1 echo. # El resultado de la orden siguiente coincide con el comando 'ls'. $PS1> echo * # La rden siguiente muestra el mismo literal que el entre comillas. $PS1> echo "* * ? *" # En este caso la shell interpreta `pwd` y $PATH, en cambio '*' y # '?' no se interpretan. $PS1> echo "* ? `pwd` $PATH" # En este caso se conserva todo el literal sin interpretar. $PS1> echo '* ? `pwd` $PATH' # A continuacion la orden mostrara dos comillas dobles. $PS1> echo \"\" # Las siguientes ordenes producen resultados cuya explicacion no # resulta trivial. Ejecutalas e intenta explicarlas recordando # lo que viste en 'man echo'. Si es necesario vuelve a consultarlo. $PS1> echo \n $PS1> echo \\n $PS1> echo \\\n $PS1> echo \\\\n Redireccion de entrada salida: ------------------------------ 1) > Redirije la salida estandar a un fichero o dispositivo. 2) < Redirije la entrada estandar tomandola desde un fichero. 3) | Comunica dos procesos por medio de entrada salida. Ojo no confundir con MSDOS. En UNIX los procesos comunican directamente pasandose los datos directamente sin crear ningun fichero temporal. El proceso que lee quedara en espera mientras el el proceso que escribe mantenga abierto el dispo- sitivo de salida estandar incluso si momentaneamente no se produce salida. Cuando el proces escritor termina cierra todos sus ficheros y el proceso lector acusara la condicion como un End-Of-File. 4) >> Redirije la salida estandar a un fichero sin sobreescribirlo. En lugar de ello aniade al final del mismo. 5) < Redirije la salida estandar de errores a un fichero o dispositivo. 7) 2>&1 Redirije la salida estandard de errores donde esta redirijido la salida estandard. (0=entrada estandar, 1=salida estandar, 2=salida de errores estandar) Ejemplos: --------- # La orden siguiente no produce ningun resultado visible porque # la salida estandar se redirije al dispositivo /dev/null. Este # dispositivo es como un pozo sin fondo. $PS1> date > /dev/null # Este dispositivo tambien resulta util para simular una entrada nula de datos. Por ejemplo para crear un fichero vacio. $PS1> cat < /dev/null > kk # El mismo efecto podriamos conseguir usando. Se puede utilizar para vaciar ficheros respetando los permisos originales. $PS1> > kk # El resultado de la orden siguiente se guardara en el fichero kk. $PS1> ps -e > kk # Mostrar el contenido del fichero kk. $PS1> cat kk # Equivale al anterior. $PS1> cat < kk # Equivale a los dos anteriores. /dev/tty es un dispositivo que # se identifica como nuestro terminal. $PS1> cat < kk > /dev/tty # Muestra paginando en pantalla el contenido de 'kk'. $PS1> more kk # Lo mismo que el anterior. $PS1> more < kk # Oredna el fichero 'kk' y lo muestra por pantalla. $PS1> sort < kk # Ejecuta who y sort. who entrega a sort y este muestra en pantalla # el resultado de who pero ordenado. $PS1> who | sort # Si asumimos que 'kk3' es un fichero no ejecutable el comando # que sigue dara un error al intentar ejecutar 'kk3'. $PS1> who | kk3 # Para ordenar unas lineas que introducimos en la propia linea de # ordenes usamos el operador '<<' seguido de una clave de # finalizacion, de la entrada. $PS1> sort < rm kk # La segunda vez fallara el comando al no existir el fichero # El mensaje de error aparecera en pantalla. $PS1> rm kk # La tercera vez tambien fallara pero no sacara mensaje, ya que # este se redirije a '/dev/null'. $PS1> rm kk 2> /dev/null # La tercera vez tambien fallara pero no sacara mensaje, ya que # este se redirije al fichero 'salida', junto con la salida estandar. $PS1> rm kk > salida 2>&1 # Observar las diferencias entre los dos comandos siguientes: $PS1> cat < cat <<-FIN gggg ssss FIN Background: ----------- Para lanzar un comando en background se termina el mismo con '&'. Si deseamos que el comando continue en ejecucion despues de salir del sistema deberemos preceder el comando con 'nohup'. El ';' ----- Para lanzar un comando a continuacion de otro se separan por ';' Los parentesis: --------------- para agrupar sentencias se puede usar parentesis. Ejemplos: # La orden siguiente busca en todo el sistema ficheros del # tipo 'kk1*'. Ojo aqui el asterisco viene entre comillas dobles # porque no deseamos que lo interprete la shell. Por tando el # programa find recibira un argumento que contiene un asterisco y # sera el propio find el que expanda dicho asterisco. La salida # estandar de errores se redigira a '/dev/null' y la salida estan- # dar se redirige al fichero 'scan.kk1'. Este proceso se lanza en # background y retorna el control a la shell. Permanecera activo # Aunque cerremos la sesion ya que viene precedido del comando # 'nohup' (no hang-up). $PS1> nohup find / -name "kk1*" 2> /dev/null -print > scan.kk1 & # La orden siguiente ejecuta dos comandos secuencialmente pero # solo el ultimo comando se lanzara en bacground $PS1> sleep 5 ; echo xxxxxxxxxxx & # La orden siguiente se lanza en background afectando el background # a ambos comandos por el echo de usar parentesis. $PS1> ( sleep 5 ; echo xxxxxxxxxxx ) & Variables de entorno: --------------------- Para definir una variable de entorno basta poner su nombre un igual y su valor. (Ojo no dejar espacios). Para consultar una variable se utiliza el nombre de la variable precedido por '$'. Para ver todas las variables y sus valores se utiliza el comando set. Para hacer la variable exportable se usa el comando 'export'. Se puede hacer que una variable sea de solo lectura con el comando 'readonly'. Este comando sin parametros mostrara todas las variables que son de solo lectura. Subshell: --------- Desde una shell se puede arrancar una subshell. Para salir a la shell padre se hace exit. Ejemplos: $PS1> ksh $PS1> ps -l $PS1> ksh $PS1> ps -l $PS1> exec ksh # Aparentemente no ha hecho nada $PS1> ps -l $PS1> exec sh $PS1> ps -l Algunas variables importantes: ------------------------------ $PATH Camino de busqueda para ejecutables. $HOME Directorio home $LOGNAME Logname $SHELL Tipo de shell usado $TERM Tipo de terminal usado $PS1 Prompt 1 "$ " $PS2 Prompt 2 "> " $PS3 Prompt 3 "#? " $PS4 Prompt 4 "+ " $? Codigo de retorno de un comando $# Numero de parametros posicionales $0 $1 $2 $3 comando argumento1 argumento2 argumento3 $* Lista de argumentos "$1 $2 $3 $4 ..." $@ Lista de argumentos "$1" "$2" "$3" "$4" .... $- Lista de opciones de la shell. $$ Identificacion del proceso. $! Idendificacio del ultimo proceso background. Ejemplos: --------- # Consultar las variables y sus valores $PS1> set # Asignar el valor 'VALOR1' a la variable 'VAR1'(Si no existe # la crea). $PS1> VAR1=VALOR1 # Asignar 'VALOR2' a la variable 'VAR2'. $PS1> VAR2=VALOR2 # Mostrar el valor de la variable 'VAR1'. (Es la shell quien expande # la variable. 'echo' es un comando que solo muestra aquello que se # le pasa como argumentos. $PS1> echo $VAR1 # Consultamos el pid de la shel actual. $PS1> echo $$ # Arrancamos otra shell. $PS1> ksh # Consultamos el PID de la nueva shell. $PS1> echo $$ # Consultamos el valor de la variable VAR1 $PS1> echo $VAR1 # Modificamos la variable PS1, y veremos como cambia inmediantamente # el prompt de nuestra shell (primer introductor). $PS1> PS1=SUBSHELL> # Ejecutamos un comando cualquiera como ls -l y vemos como el # introductor sigue cambiado. $PS1> ls -l # Si salimos de esta shell recuperamos la shell anterior y por tanto # se reestablecen los primitivos valores de todas las variables # incluidas la variable PS1 que volvera a tener el valor original. $PS1> exit # Consultamos el PID y vemos que es el PID de la primera shell. $PS1> echo $$ # hacemos exportable la variable VAR1. $PS1> export VAR1 # Hacemos las dos de solo lectura pero recordar que VAR2 no la # hemos hecho exportable. $PS1> readonly VAR1 VAR2 # Volvemos a arrancar una nueva shell. $PS1> ksh # Consultamos nuevamente VAR1 $PS1> echo $VAR1 # Intentamos alterar el valor de 'VAR1' pero es de solo lectura. $PS1> VAR1=xxx # VAR2 no existe porque no la hemos exportado. Por lo tanto se # va a crear ahora sin problemas con el valor 'xxx'. $PS1> VAR2=xxx # Ahora creamos una variable 'AQUI' que contendra el camino actual. $PS1> AQUI=`pwd` # Cambiamos a directorio $HOME $PS1> cd $HOME # Cambiamos al directorio donde estabamos antes. $PS1> cd $AQUI # Consultamos el codigo de retorno del ultimo comando # que retornara 0 (0= OK). $PS1> echo $? # Intenmos cambiar a un directorio que no existe. $PS1> cd 12345 # Consultamos el codigo de retorno que sera distinto de 0. $PS1> echo $? Shell-script ------------ Puesto que la shell es un programa totalmente normal, se puede ejecutar una shell redirigiendo su entrada estandar. sh < entrada Tambien se puede ejecutar redirigiendo entrada y salida. sh < entrada > salida Ejemplos: --------- # Ejecutar el comando echo y redirigir la salida al fichero # comando date. (El fichero contendra la palabra 'date'). $PS1> echo date > comandodate # Ejecutar una shel que tome como entrada el fichero anterior. $PS1> ksh < comandodate # Intentar ejecutar el fichero 'comandedate' directamente # Fallara por que no tiene permiso de ejecucion. $PS1> comandodate # Dar permiso de ejecucion al fichero 'comandodate'. $PS1> chmod +x comandodate # Ejecutar ahora el fichero 'comandodate' equivaldra a ejecutar 'date' $PS1> comandodate # Crear un fichero que contenga 'VAR=55' $PS1> echo VAR=55 > asignacion # Le damos permiso de ejecucion $PS1> chmod +x asignacion # Ejecutamos el comando (shell-script) 'asignacion' $PS1> asignacion # Ahora consultamos el valor de la variable 'VAR'. # Comprobaremos que la variable no tiene valor. Esto asi porque # La ejecucion de 'asignacion' se hace arrancando una subshell. # Por ello cuando termine el comando retornara a la shell primera # recuperando todo el entorno de variables correspondiente. $PS1> echo $VAR # Para que esto no ocurra hay que arrancar el comando mediante # un punto y un blanco por delante del comando. Esto indica a la # shell que no arraque una subshell sino que tome como entrada el # fichero y lo ejecute con su propio entorno de variables. $PS1> . asignacion # Ahora vemos que la variable si ha sido creada y mantiene su valor. $PS1> echo $VAR # Cambiamos la directorio $HOME $PS1> cd # Vamos a alterar el valor de la variable 'TERM' (Normalmente # contendra el tipo de terminal que tenemos). $PS1> TERM=KK # Ejecutamos nuestro '.profile' con la shel actual. $PS1> . .profile # Vemos que se reestablece el valor original. # En el .profile se establecen muchas variables de utilidad para # nuestra sesion. Es frecuente que necesitemos personalizar dicha # shell. Es conveniente aniadir las modificaciones personales # destacando con un comentario que dichas modificaciones las hemos # realizado nosotros mismos. $PS1> echo $TERM # Las variables de UNIX de un determinado proceso no pueden # ser alteradas por ningun otro proceso. rutina() { echo echo "Comienzo subrutina $1 VAR=$VAR" sleep 3 echo "Final subrutina $1 VAR=$VAR" } VAR="Contenido 1" rutina 1 & sleep 1 VAR="Contenido 2" rutina 2 & sleep 1 VAR="Contenido 3" rutina 3 & sleep 1 VAR="Contenido 4" rutina 4 & sleep 1 VAR="Contenido 5" rutina 5 & sleep 1 VAR="Contenido 6" rutina 6 & # Cuando queremos agrupar comandos podemos usar parentesis. # En la bourne-shell no se pueden anidar parentesis pero en # la kshell si. Cada par de parentesis arranca una subshell. # Supongamos que deseamos agrupar una serie de comandos para # aplicarles 'time', y que la salida estandar de errores de # todo ello (incluido el time) queremos redirigirla a un # fichero. (La salida normal de time es por la salida estandar # de errores). #[Para salida] #|------------------------------------------| #| [Para time] | #| |---------------------------------| | ( time ( sleep 5 ; echo "hola" ; sleep 5 ) ) 2> fichero more fichero PROBLEMA Suponte que te situas en un directorio y en el se encuentra un fichero ejecutable con todos los permisos. Dicho comando se llama "nostoi" y nosotros tecleamos el comando "nos*". El sistema parece burlarse de nosotros porque responde "nostoy: not found". Es decir el sistema parece haber localizado el nombre completo pero dice que no lo encuentra. Que ha pasado?